1 /*===========================================================================
2  Copyright (c) 1998-2000, The Santa Cruz Operation
3  All rights reserved.
4 
5  Redistribution and use in source and binary forms, with or without
6  modification, are permitted provided that the following conditions are met:
7 
8  *Redistributions of source code must retain the above copyright notice,
9  this list of conditions and the following disclaimer.
10 
11  *Redistributions in binary form must reproduce the above copyright notice,
12  this list of conditions and the following disclaimer in the documentation
13  and/or other materials provided with the distribution.
14 
15  *Neither name of The Santa Cruz Operation nor the names of its contributors
16  may be used to endorse or promote products derived from this software
17  without specific prior written permission.
18 
19  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
20  IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
23  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  INTERRUPTION)
27  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
30  DAMAGE.
31  =========================================================================*/
32 
33 
34 /*
35  *	gtags-cscope - interactive C symbol cross-reference (cscope)
36  *
37  *	main functions
38  */
39 
40 #include "global-cscope.h"
41 #include "char.h"
42 #include "strbuf.h"
43 #include "build.h"
44 #include "version-cscope.h"	/* FILEVERSION and FIXVERSION */
45 
46 /* for libutil */
47 #include "env.h"
48 #include "gparam.h"
49 #include "path.h"
50 #include "test.h"
51 #include "version.h"
52 /* usage */
53 #include "const.h"
54 
55 #include <stdlib.h>	/* atoi */
56 #include <unistd.h>
57 #if defined(USE_NCURSES) && !defined(RENAMED_NCURSES)
58 #include <ncurses.h>
59 #else
60 #include <curses.h>
61 #endif
62 #include <sys/types.h>	/* needed by stat.h */
63 #include <sys/stat.h>	/* stat */
64 #include <signal.h>
65 
66 #if defined(_WIN32) && !defined(__CYGWIN__)
67 #define S_IRWXG 0070
68 #define S_IRWXO 0007
69 #define mkdir(path,mode) mkdir(path)
70 #endif
71 
72 /* defaults for unset environment variables */
73 /** text editor */
74 #if defined(__DJGPP__) || (defined(_WIN32) && !defined(__CYGWIN__))
75 #define EDITOR	"tde"
76 #else
77 #define EDITOR	"vi"
78 #endif
79 
80 /** no $HOME --> use root directory */
81 #define HOME	"/"
82 /** shell executable */
83 #define	SHELL	"sh"
84 
85 /** default: used by vi and emacs */
86 #define LINEFLAG "+%s"
87 /** temp dir */
88 #define TMPDIR	"/tmp"
89 
90 char	*editor, *shell, *lineflag;	/**< environment variables */
91 char	*global_command;	/**< "global" by default */
92 char	*gtags_command;		/**< "gtags" by default */
93 char	*home;			/**< Home directory */
94 BOOL	lineflagafterfile;
95 char	*argv0;			/**< command name */
96 int	dispcomponents = 1;	/**< file path components to display */
97 #if CCS
98 BOOL	displayversion;		/**< display the C Compilation System version */
99 #endif
100 BOOL	editallprompt = YES;	/**< prompt between editing files */
101 BOOL	incurses = NO;		/**< in curses */
102 BOOL	isuptodate;		/**< consider the crossref up-to-date */
103 BOOL	linemode = NO;		/**< use line oriented user interface */
104 BOOL	verbosemode = NO;	/**< print extra information on line mode */
105 BOOL	absolutepath = NO;	/**< print absolute path name */
106 BOOL	ignoresigint = NO;	/**< ignore SIGINT signal */
107 BOOL	ogs;			/**< display OGS book and subsystem names */
108 char	*prependpath;		/**< prepend path to file names */
109 FILE	*refsfound;		/**< references found file */
110 char	temp1[PATHLEN + 1];	/**< temporary file name */
111 char	temp2[PATHLEN + 1];	/**< temporary file name */
112 char	tempdirpv[PATHLEN + 1];	/**< private temp directory */
113 char	tempstring[TEMPSTRING_LEN + 1]; /**< use this as a buffer, instead of yytext,
114 				 * which had better be left alone */
115 char	*tmpdir;		/**< temporary directory */
116 
117 static	BOOL	onesearch;		/**< one search only in line mode */
118 static	char	*reflines;		/**< symbol reference lines file */
119 
120 /* Internal prototypes: */
121 static	void	longusage(void);
122 static	void	usage(void);
123 int	qflag;
124 
125 #ifdef HAVE_FIXKEYPAD
126 void	fixkeypad();
127 #endif
128 
129 #if defined(KEY_RESIZE) && defined(SIGWINCH)
130 void
sigwinch_handler(int sig,siginfo_t * info,void * unused)131 sigwinch_handler(int sig, siginfo_t *info, void *unused)
132 {
133     (void) sig;
134     (void) info;
135     (void) unused;
136     if(incurses == YES)
137         ungetch(KEY_RESIZE);
138 }
139 #endif
140 
141 int
main(int argc,char ** argv)142 main(int argc, char **argv)
143 {
144     char *s;
145     int c;
146     pid_t pid;
147     struct stat	stat_buf;
148     mode_t orig_umask;
149 #if defined(KEY_RESIZE) && defined(SIGWINCH)
150     struct sigaction winch_action;
151 #endif
152 
153     /* save the command name for messages */
154     argv0 = argv[0];
155 
156     /* set the options */
157     while (--argc > 0 && (*++argv)[0] == '-') {
158 	/* HBB 20030814: add GNU-style --help and --version options */
159 	if (strequal(argv[0], "--help")
160 	    || strequal(argv[0], "-h")) {
161 	    longusage();
162 	    myexit(0);
163 	}
164 	if (strequal(argv[0], "--version")
165 	    || strequal(argv[0], "-V")) {
166 #if CCS
167 	    displayversion = YES;
168 #else
169 	    fprintf(stderr, "%s: %s (based on cscope version %d%s)\n", argv0, get_version(),
170 		    FILEVERSION, FIXVERSION);
171 	    myexit(0);
172 #endif
173 	}
174 
175 	for (s = argv[0] + 1; *s != '\0'; s++) {
176 
177 	    /* look for an input field number */
178 	    if (isdigit((unsigned char) *s)) {
179 		field = *s - '0';
180 		if (field > 8) {
181 		    field = 8;
182 		}
183 		if (*++s == '\0' && --argc > 0) {
184 		    s = *++argv;
185 		}
186 		if (strlen(s) > PATLEN) {
187 		    postfatal("\
188 gtags-cscope: pattern too long, cannot be > %d characters\n", PATLEN);
189 		    /* NOTREACHED */
190 		}
191 		strcpy(Pattern, s);
192 		goto nextarg;
193 	    }
194 	    switch (*s) {
195 	    case '-':	/* end of options */
196 		--argc;
197 		++argv;
198 		goto lastarg;
199 	    case 'a':	/* absolute path name */
200 		absolutepath = YES;
201 		break;
202 	    case 'b':	/* only build the cross-reference */
203 		buildonly = YES;
204 		linemode  = YES;
205 		break;
206 	    case 'c':	/* ASCII characters only in crossref */
207 		/* N/A */
208 		break;
209 	    case 'C':	/* turn on caseless mode for symbol searches */
210 		caseless = YES;
211 		break;
212 	    case 'd':	/* consider crossref up-to-date */
213 		isuptodate = YES;
214 		break;
215 	    case 'e':	/* suppress ^E prompt between files */
216 		editallprompt = NO;
217 		break;
218 	    case 'i':	/* ignore SIGINT signal */
219 		ignoresigint = YES;
220 		break;
221 	    case 'k':	/* ignore DFLT_INCDIR */
222 		/* N/A */
223 		break;
224 	    case 'L':
225 		onesearch = YES;
226 		/* FALLTHROUGH */
227 	    case 'l':
228 		linemode = YES;
229 		break;
230 	    case 'v':
231 		verbosemode = YES;
232 		break;
233 	    case 'o':	/* display OGS book and subsystem names */
234 		ogs = YES;
235 		break;
236 	    case 'q':	/* quick search */
237 		/* N/A */
238 		break;
239 	    case 'T':	/* truncate symbols to 8 characters */
240 		/* N/A */
241 		break;
242 	    case 'u':	/* unconditionally build the cross-reference */
243 		/* N/A */
244 		break;
245 	    case 'U':	/* assume some files have changed */
246 		/* N/A */
247 		break;
248 	    case 'R':
249 		usage();
250 		break;
251 	    case 'f':	/* alternate cross-reference file */
252 	    case 'F':	/* symbol reference lines file */
253 /*	    case 'i':	   file containing file names */
254 	    case 'I':	/* #include file directory */
255 	    case 'p':	/* file path components to display */
256 	    case 'P':	/* prepend path to file names */
257 	    case 's':	/* additional source file directory */
258 	    case 'S':
259 		c = *s;
260 		if (*++s == '\0' && --argc > 0) {
261 		    s = *++argv;
262 		}
263 		if (*s == '\0') {
264 		    fprintf(stderr, "%s: -%c option: missing or empty value\n",
265 			    argv0, c);
266 		    goto usage;
267 		}
268 		switch (c) {
269 		case 'f':	/* alternate cross-reference file (default: cscope.out) */
270 		    /* N/A */
271 		    break;
272 		case 'F':	/* symbol reference lines file */
273 		    reflines = s;
274 		    break;
275 		case 'i':	/* file containing file names (default: cscope.files) */
276 		    /* N/A */
277 		    break;
278 		case 'I':	/* #include file directory */
279 		    /* N/A */
280 		    break;
281 		case 'p':	/* file path components to display */
282 		    if (*s < '0' || *s > '9' ) {
283 			fprintf(stderr, "\
284 %s: -p option: missing or invalid numeric value\n",
285 				argv0);
286 			goto usage;
287 		    }
288 		    dispcomponents = atoi(s);
289 		    break;
290 		case 'P':	/* prepend path to file names */
291 		    /* N/A */
292 		    break;
293 		case 's':	/* additional source directory */
294 		case 'S':
295 		    /* N/A */
296 		    break;
297 		}
298 		goto nextarg;
299 	    default:
300 		fprintf(stderr, "%s: unknown option: -%c\n", argv0,
301 			*s);
302 	    usage:
303 		usage();
304 		fprintf(stderr, "Try the -h option for more information.\n");
305 		myexit(1);
306 	    } /* switch(option letter) */
307 	} /* for(option) */
308     nextarg:
309 	;
310     } /* while(argv) */
311 
312  lastarg:
313     /* read the environment */
314     editor = mygetenv("EDITOR", EDITOR);
315     editor = mygetenv("VIEWER", editor); /* use viewer if set */
316     editor = mygetenv("CSCOPE_EDITOR", editor);	/* has last word */
317     home = mygetenv("HOME", HOME);
318     global_command = mygetenv("GTAGSGLOBAL", "global");
319     gtags_command = mygetenv("GTAGSGTAGS", "gtags");
320 #if defined(_WIN32) || defined(__DJGPP__)
321     shell = mygetenv("COMSPEC", SHELL);
322     shell = mygetenv("SHELL", shell);
323     tmpdir = mygetenv("TMP", TMPDIR);
324     tmpdir = mygetenv("TMPDIR", tmpdir);
325 #else
326     shell = mygetenv("SHELL", SHELL);
327     tmpdir = mygetenv("TMPDIR", TMPDIR);
328 #endif
329     lineflag = mygetenv("CSCOPE_LINEFLAG", LINEFLAG);
330     lineflagafterfile = getenv("CSCOPE_LINEFLAG_AFTER_FILE") ? 1 : 0;
331 
332     /* make sure that tmpdir exists */
333     if (lstat (tmpdir, &stat_buf)) {
334 	fprintf (stderr, "\
335 cscope: Temporary directory %s does not exist or cannot be accessed\n",
336 		 tmpdir);
337 	fprintf (stderr, "\
338 cscope: Please create the directory or set the environment variable\n\
339 cscope: TMPDIR to a valid directory\n");
340 	myexit(1);
341     }
342 
343     /* create the temporary file names */
344     orig_umask = umask(S_IRWXG|S_IRWXO);
345     pid = getpid();
346     snprintf(tempdirpv, sizeof(tempdirpv), "%s/cscope.%d", tmpdir, pid);
347     if(mkdir(tempdirpv,S_IRWXU)) {
348 	fprintf(stderr, "\
349 cscope: Could not create private temp dir %s\n",
350 		tempdirpv);
351 	myexit(1);
352     }
353     umask(orig_umask);
354 
355     snprintf(temp1, sizeof(temp1), "%s/cscope.1", tempdirpv);
356     snprintf(temp2, sizeof(temp2), "%s/cscope.2", tempdirpv);
357 
358     /* if running in the foreground */
359     if (signal(SIGINT, SIG_IGN) != SIG_IGN && ignoresigint == NO) {
360 	/* cleanup on the interrupt and quit signals */
361 	signal(SIGINT, myexit);
362 #ifdef SIGQUIT
363 	signal(SIGQUIT, myexit);
364 #endif
365     }
366     /* cleanup on the hangup signal */
367 #ifdef SIGHUP
368     signal(SIGHUP, myexit);
369 #endif
370 
371     /* ditto the TERM signal */
372     signal(SIGTERM, myexit);
373 
374     if (linemode == NO) {
375 	signal(SIGINT, SIG_IGN);	/* ignore interrupts */
376 #ifdef SIGPIPE
377 	signal(SIGPIPE, SIG_IGN);/* | command can cause pipe signal */
378 #endif
379 
380 #if defined(KEY_RESIZE) && defined(SIGWINCH)
381 	winch_action.sa_sigaction = sigwinch_handler;
382 	sigemptyset(&winch_action.sa_mask);
383 	winch_action.sa_flags = SA_SIGINFO;
384 	sigaction(SIGWINCH,&winch_action,NULL);
385 #endif
386 
387 	/* initialize the curses display package */
388 	initscr();	/* initialize the screen */
389 	entercurses();
390 #if TERMINFO
391 	keypad(stdscr, TRUE);	/* enable the keypad */
392 # ifdef HAVE_FIXKEYPAD
393 	fixkeypad();	/* fix for getch() intermittently returning garbage */
394 # endif
395 #endif /* TERMINFO */
396 #if UNIXPC
397 	standend();	/* turn off reverse video */
398 #endif
399 	dispinit();	/* initialize display parameters */
400 	setfield();	/* set the initial cursor position */
401 	clearmsg();	/* clear any build progress message */
402 	display();	/* display the version number and input fields */
403     }
404 
405     /* if the cross-reference is to be considered up-to-date */
406     if (isuptodate == YES) {
407 	STRBUF  *sb = strbuf_open(0);
408 	strbuf_sprintf(sb, "%s -p >" NULL_DEVICE, quote_shell(global_command));
409 	if (system(strbuf_value(sb)) != 0) {
410 	    postfatal("gtags-cscope: GTAGS not found. Please invoke again without -d option.\n");
411             /* NOTREACHED */
412 	}
413 	strbuf_close(sb);
414     } else {
415 	if (linemode == NO || verbosemode == YES)    /* display if verbose as well */
416 	    postmsg("Building cross-reference...");
417 	rebuild();
418 	if (linemode == NO )
419             clearmsg(); /* clear any build progress message */
420 	if (buildonly == YES) {
421 	    myexit(0);
422 	}
423     }
424 
425     /* opendatabase(); */
426 
427     /* if using the line oriented user interface so cscope can be a
428        subprocess to emacs or samuel */
429     if (linemode == YES) {
430 	if (*Pattern != '\0') {		/* do any optional search */
431 	    if (search() == YES) {
432 		/* print the total number of lines in
433 		 * verbose mode */
434 		if (verbosemode == YES)
435 		    printf("cscope: %d lines\n",
436 			   totallines);
437 
438 		while ((c = getc(refsfound)) != EOF)
439 		    putchar(c);
440 	    }
441 	}
442 	if (onesearch == YES)
443 	    myexit(0);
444 
445 	for (;;) {
446 	    char buf[PATLEN + 2];
447 
448 	    printf(">> ");
449 	    fflush(stdout);
450 	    if (fgets(buf, sizeof(buf), stdin) == NULL) {
451 		myexit(0);
452 	    }
453 	    /* remove any trailing newline character */
454 	    if (*(s = buf + strlen(buf) - 1) == '\n') {
455 		*s = '\0';
456 	    }
457 	    switch (*buf) {
458 	    case '0':
459 	    case '1':
460 	    case '2':
461 	    case '3':
462 	    case '4':
463 	    case '5':
464 	    case '6':
465 	    case '7':
466 	    case '8':
467 	    case '9':	/* Vim 8.0 calls this function. */
468 		field = *buf - '0';
469 		strcpy(Pattern, buf + 1);
470 		search();
471 		printf("cscope: %d lines\n", totallines);
472 		while ((c = getc(refsfound)) != EOF) {
473 		    putchar(c);
474 		}
475 		break;
476 
477 	    case 'c':	/* toggle caseless mode */
478 	    case ctrl('C'):
479 		if (caseless == NO) {
480 		    caseless = YES;
481 		} else {
482 		    caseless = NO;
483 		}
484 		break;
485 
486 	    case 'r':	/* rebuild database cscope style */
487 	    case ctrl('R'):
488 		rebuild();
489 		putchar('\n');
490 		break;
491 
492 	    case 'C':	/* clear file names */
493 		/* N/A */
494 		putchar('\n');
495 		break;
496 
497 	    case 'F':	/* add a file name */
498 		/* N/A */
499 		putchar('\n');
500 		break;
501 
502 	    case 'q':	/* quit */
503 	    case ctrl('D'):
504 	    case ctrl('Z'):
505 		myexit(0);
506 
507 	    default:
508 		fprintf(stderr, "gtags-cscope: unknown command '%s'\n", buf);
509 		break;
510 	    }
511 	}
512 	/* NOTREACHED */
513     }
514     /* do any optional search */
515     if (*Pattern != '\0') {
516 	atfield();		/* move to the input field */
517 	command(ctrl('Y'));	/* search */
518     } else if (reflines != NULL) {
519 	/* read any symbol reference lines file */
520 	readrefs(reflines);
521     }
522     display();		/* update the display */
523 
524     for (;;) {
525 	if (!selecting)
526 	    atfield();	/* move to the input field */
527 
528 	/* exit if the quit command is entered */
529 	if ((c = mygetch()) == EOF || c == ctrl('D') || c == ctrl('Z')) {
530 	    break;
531 	}
532 	/* execute the commmand, updating the display if necessary */
533 	if (command(c) == YES) {
534 	    display();
535 	}
536 
537 	if (selecting) {
538 	    move(displine[curdispline], 0);
539 	    refresh();
540 	}
541     }
542     /* cleanup and exit */
543     myexit(0);
544     /* NOTREACHED */
545     return 0;		/* avoid warning... */
546 }
547 
548 void
cannotopen(char * file)549 cannotopen(char *file)
550 {
551     posterr("Cannot open file %s", file);
552 }
553 
554 /* FIXME MTE - should use postfatal here */
555 void
cannotwrite(char * file)556 cannotwrite(char *file)
557 {
558     char	msg[MSGLEN + 1];
559 
560     snprintf(msg, sizeof(msg), "Removed file %s because write failed", file);
561     myperror(msg);	/* display the reason */
562 
563     unlink(file);
564     myexit(1);	/* calls exit(2), which closes files */
565 }
566 
567 /** enter curses mode */
568 void
entercurses(void)569 entercurses(void)
570 {
571     incurses = YES;
572 #ifndef __MSDOS__ /* HBB 20010313 */
573     nonl();		    /* don't translate an output \n to \n\r */
574 #endif
575     raw();			/* single character input */
576     noecho();			/* don't echo input characters */
577     clear();			/* clear the screen */
578     mouseinit();		/* initialize any mouse interface */
579     drawscrollbar(topline, nextline);
580 }
581 
582 
583 /** exit curses mode */
584 void
exitcurses(void)585 exitcurses(void)
586 {
587 	/* clear the bottom line */
588 	move(LINES - 1, 0);
589 	clrtoeol();
590 	refresh();
591 
592 	/* exit curses and restore the terminal modes */
593 	endwin();
594 	incurses = NO;
595 
596 	/* restore the mouse */
597 	mousecleanup();
598 	fflush(stdout);
599 }
600 
601 
602 /** normal usage message */
603 static void
usage(void)604 usage(void)
605 {
606 	fputs(usage_const, stderr);
607 }
608 
609 
610 /** long usage message */
611 static void
longusage(void)612 longusage(void)
613 {
614 	fputs(usage_const, stdout);
615         fputs(help_const, stdout);
616 }
617 
618 /** cleanup and exit */
619 
620 void
myexit(int sig)621 myexit(int sig)
622 {
623 	/* HBB 20010313; close file before unlinking it. Unix may not care
624 	 * about that, but DOS absolutely needs it */
625 	if (refsfound != NULL)
626 		fclose(refsfound);
627 
628 	/* remove any temporary files */
629 	if (temp1[0] != '\0') {
630 		unlink(temp1);
631 		unlink(temp2);
632 		rmdir(tempdirpv);
633 	}
634 	/* restore the terminal to its original mode */
635 	if (incurses == YES) {
636 		exitcurses();
637 	}
638 	/* dump core for debugging on the quit signal */
639 #ifdef SIGQUIT
640 	if (sig == SIGQUIT) {
641 		abort();
642 	}
643 #endif
644 	/* HBB 20000421: be nice: free allocated data */
645 	exit(sig);
646 }
647