xref: /Universal-ctags/main/main.c (revision eed4431b6359e8abcd517010da6a38608c456d19)
1 /*
2 *   Copyright (c) 1996-2003, Darren Hiebert
3 *
4 *   Author: Darren Hiebert <dhiebert@users.sourceforge.net>
5 *           http://ctags.sourceforge.net
6 *
7 *   This source code is released for free distribution under the terms of the
8 *   GNU General Public License version 2 or (at your option) any later version.
9 *   It is provided on an as-is basis and no responsibility is accepted for its
10 *   failure to perform as expected.
11 *
12 *   This is a reimplementation of the ctags (1) program. It is an attempt to
13 *   provide a fully featured ctags program which is free of the limitations
14 *   which most (all?) others are subject to.
15 *
16 *   This module contains the start-up code and routines to determine the list
17 *   of files to parsed for tags.
18 */
19 
20 /*
21 *   INCLUDE FILES
22 */
23 #include "general.h"  /* must always come first */
24 
25 #if HAVE_DECL___ENVIRON
26 #include <unistd.h>
27 #elif HAVE_DECL__NSGETENVIRON
28 #include <crt_externs.h>
29 #endif
30 
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 
35 /*  To provide directory searching for recursion feature.
36  */
37 
38 #ifdef HAVE_DIRENT_H
39 # ifdef HAVE_SYS_TYPES_H
40 #  include <sys/types.h>  /* required by dirent.h */
41 # endif
42 # include <dirent.h>  /* to declare opendir() */
43 #endif
44 #ifdef HAVE_DIRECT_H
45 # include <direct.h>  /* to _getcwd() */
46 #endif
47 #ifdef HAVE_IO_H
48 # include <io.h>  /* to declare _findfirst() */
49 #endif
50 
51 
52 #include "ctags.h"
53 #include "debug.h"
54 #include "entry_p.h"
55 #include "error_p.h"
56 #include "field_p.h"
57 #include "keyword_p.h"
58 #include "main_p.h"
59 #include "options_p.h"
60 #include "optscript.h"
61 #include "parse_p.h"
62 #include "read_p.h"
63 #include "routines_p.h"
64 #include "stats_p.h"
65 #include "trace.h"
66 #include "trashbox_p.h"
67 #include "writer_p.h"
68 #include "xtag_p.h"
69 
70 #ifdef HAVE_JANSSON
71 #include "interactive_p.h"
72 #include <jansson.h>
73 #include <errno.h>
74 #endif
75 
76 /*
77 *   DATA DEFINITIONS
78 */
79 static mainLoopFunc mainLoop;
80 static void *mainData;
81 
82 /*
83 *   FUNCTION PROTOTYPES
84 */
85 static bool createTagsForEntry (const char *const entryName);
86 
87 /*
88 *   FUNCTION DEFINITIONS
89 */
90 
91 #if defined (HAVE_OPENDIR) && (defined (HAVE_DIRENT_H) || defined (_MSC_VER))
recurseUsingOpendir(const char * const dirName)92 static bool recurseUsingOpendir (const char *const dirName)
93 {
94 	bool resize = false;
95 	DIR *const dir = opendir (dirName);
96 	if (dir == NULL)
97 		error (WARNING | PERROR, "cannot recurse into directory \"%s\"", dirName);
98 	else
99 	{
100 		struct dirent *entry;
101 		while ((entry = readdir (dir)) != NULL)
102 		{
103 			if (strcmp (entry->d_name, ".") != 0  &&
104 				strcmp (entry->d_name, "..") != 0)
105 			{
106 				char *filePath;
107 				bool free_p = false;
108 				if (strcmp (dirName, ".") == 0)
109 					filePath = entry->d_name;
110 				else
111 				{
112 					filePath = combinePathAndFile (dirName, entry->d_name);
113 					free_p = true;
114 				}
115 				resize |= createTagsForEntry (filePath);
116 				if (free_p)
117 					eFree (filePath);
118 			}
119 		}
120 		closedir (dir);
121 	}
122 	return resize;
123 }
124 #endif
125 
126 #ifdef HAVE__FINDFIRST
127 
createTagsForWildcardEntry(const char * const pattern,const size_t dirLength,const char * const entryName)128 static bool createTagsForWildcardEntry (
129 		const char *const pattern, const size_t dirLength,
130 		const char *const entryName)
131 {
132 	bool resize = false;
133 	/* we must not recurse into the directories "." or ".." */
134 	if (strcmp (entryName, ".") != 0  &&  strcmp (entryName, "..") != 0)
135 	{
136 		vString *const filePath = vStringNew ();
137 		vStringNCopyS (filePath, pattern, dirLength);
138 		vStringCatS (filePath, entryName);
139 		resize = createTagsForEntry (vStringValue (filePath));
140 		vStringDelete (filePath);
141 	}
142 	return resize;
143 }
144 
createTagsForWildcardUsingFindfirst(const char * const pattern)145 static bool createTagsForWildcardUsingFindfirst (const char *const pattern)
146 {
147 	bool resize = false;
148 	const size_t dirLength = baseFilename (pattern) - pattern;
149 	struct _finddata_t fileInfo;
150 	intptr_t hFile = _findfirst (pattern, &fileInfo);
151 	if (hFile != -1L)
152 	{
153 		do
154 		{
155 			const char *const entry = (const char *) fileInfo.name;
156 			resize |= createTagsForWildcardEntry (pattern, dirLength, entry);
157 		} while (_findnext (hFile, &fileInfo) == 0);
158 		_findclose (hFile);
159 	}
160 	return resize;
161 }
162 
163 #endif
164 
165 
recurseIntoDirectory(const char * const dirName)166 static bool recurseIntoDirectory (const char *const dirName)
167 {
168 	static unsigned int recursionDepth = 0;
169 
170 	recursionDepth++;
171 
172 	bool resize = false;
173 	if (isRecursiveLink (dirName))
174 		verbose ("ignoring \"%s\" (recursive link)\n", dirName);
175 	else if (! Option.recurse)
176 		verbose ("ignoring \"%s\" (directory)\n", dirName);
177 	else if(recursionDepth > Option.maxRecursionDepth)
178 		verbose ("not descending in directory \"%s\" (depth %u > %u)\n",
179 				dirName, recursionDepth, Option.maxRecursionDepth);
180 	else
181 	{
182 		verbose ("RECURSING into directory \"%s\"\n", dirName);
183 #if defined (HAVE_OPENDIR) && (defined (HAVE_DIRENT_H) || defined (_MSC_VER))
184 		resize = recurseUsingOpendir (dirName);
185 #elif defined (HAVE__FINDFIRST)
186 		{
187 			vString *const pattern = vStringNew ();
188 			vStringCopyS (pattern, dirName);
189 			vStringPut (pattern, OUTPUT_PATH_SEPARATOR);
190 			vStringCatS (pattern, "*.*");
191 			resize = createTagsForWildcardUsingFindfirst (vStringValue (pattern));
192 			vStringDelete (pattern);
193 		}
194 #endif
195 	}
196 
197 	recursionDepth--;
198 
199 	return resize;
200 }
201 
createTagsForEntry(const char * const entryName)202 static bool createTagsForEntry (const char *const entryName)
203 {
204 	bool resize = false;
205 	fileStatus *status = eStat (entryName);
206 
207 	Assert (entryName != NULL);
208 	if (isExcludedFile (entryName, true))
209 		verbose ("excluding \"%s\" (the early stage)\n", entryName);
210 	else if (status->isSymbolicLink  &&  ! Option.followLinks)
211 		verbose ("ignoring \"%s\" (symbolic link)\n", entryName);
212 	else if (! status->exists)
213 		error (WARNING | PERROR, "cannot open input file \"%s\"", entryName);
214 	else if (status->isDirectory)
215 		resize = recurseIntoDirectory (entryName);
216 	else if (! status->isNormalFile)
217 		verbose ("ignoring \"%s\" (special file)\n", entryName);
218 	else if (isExcludedFile (entryName, false))
219 		verbose ("excluding \"%s\"\n", entryName);
220 	else
221 		resize = parseFile (entryName);
222 
223 	eStatFree (status);
224 	return resize;
225 }
226 
227 #ifdef MANUAL_GLOBBING
228 
createTagsForWildcardArg(const char * const arg)229 static bool createTagsForWildcardArg (const char *const arg)
230 {
231 	bool resize = false;
232 	vString *const pattern = vStringNewInit (arg);
233 	char *patternS = vStringValue (pattern);
234 
235 #if defined (HAVE__FINDFIRST)
236 	/*  We must transform the "." and ".." forms into something that can
237 	 *  be expanded by the _findfirst function.
238 	 */
239 	if (Option.recurse  &&
240 		(strcmp (patternS, ".") == 0  ||  strcmp (patternS, "..") == 0))
241 	{
242 		vStringPut (pattern, OUTPUT_PATH_SEPARATOR);
243 		vStringCatS (pattern, "*.*");
244 	}
245 	resize |= createTagsForWildcardUsingFindfirst (patternS);
246 #endif
247 	vStringDelete (pattern);
248 	return resize;
249 }
250 
251 #endif
252 
createTagsForArgs(cookedArgs * const args)253 static bool createTagsForArgs (cookedArgs *const args)
254 {
255 	bool resize = false;
256 
257 	/*  Generate tags for each argument on the command line.
258 	 */
259 	while (! cArgOff (args))
260 	{
261 		const char *const arg = cArgItem (args);
262 
263 #ifdef MANUAL_GLOBBING
264 		resize |= createTagsForWildcardArg (arg);
265 #else
266 		resize |= createTagsForEntry (arg);
267 #endif
268 		cArgForth (args);
269 		parseCmdlineOptions (args);
270 	}
271 	return resize;
272 }
273 
274 /*  Read from an opened file a list of file names for which to generate tags.
275  */
createTagsFromFileInput(FILE * const fp,const bool filter)276 static bool createTagsFromFileInput (FILE *const fp, const bool filter)
277 {
278 	bool resize = false;
279 	if (fp != NULL)
280 	{
281 		cookedArgs *args = cArgNewFromLineFile (fp);
282 		parseCmdlineOptions (args);
283 		while (! cArgOff (args))
284 		{
285 			resize |= createTagsForEntry (cArgItem (args));
286 			if (filter)
287 			{
288 				if (Option.filterTerminator != NULL)
289 					fputs (Option.filterTerminator, stdout);
290 				fflush (stdout);
291 			}
292 			cArgForth (args);
293 			parseCmdlineOptions (args);
294 		}
295 		cArgDelete (args);
296 	}
297 	return resize;
298 }
299 
300 /*  Read from a named file a list of file names for which to generate tags.
301  */
createTagsFromListFile(const char * const fileName)302 static bool createTagsFromListFile (const char *const fileName)
303 {
304 	bool resize;
305 	Assert (fileName != NULL);
306 	if (strcmp (fileName, "-") == 0)
307 		resize = createTagsFromFileInput (stdin, false);
308 	else
309 	{
310 		FILE *const fp = fopen (fileName, "r");
311 		if (fp == NULL)
312 			error (FATAL | PERROR, "cannot open list file \"%s\"", fileName);
313 		resize = createTagsFromFileInput (fp, false);
314 		fclose (fp);
315 	}
316 	return resize;
317 }
318 
etagsInclude(void)319 static bool etagsInclude (void)
320 {
321 	return (bool)(Option.etags && Option.etagsInclude != NULL);
322 }
323 
setMainLoop(mainLoopFunc func,void * data)324 extern void setMainLoop (mainLoopFunc func, void *data)
325 {
326 	mainLoop = func;
327 	mainData = data;
328 }
329 
runMainLoop(cookedArgs * args)330 static void runMainLoop (cookedArgs *args)
331 {
332 	(* mainLoop) (args, mainData);
333 }
334 
batchMakeTags(cookedArgs * args,void * user CTAGS_ATTR_UNUSED)335 static void batchMakeTags (cookedArgs *args, void *user CTAGS_ATTR_UNUSED)
336 {
337 	clock_t timeStamps [3];
338 	bool resize = false;
339 	bool files = (bool)(! cArgOff (args) || Option.fileList != NULL
340 							  || Option.filter);
341 
342 	if (! files)
343 	{
344 		if (filesRequired ())
345 			error (FATAL, "No files specified. Try \"%s --help\".",
346 				getExecutableName ());
347 		else if (! Option.recurse && ! etagsInclude ())
348 			return;
349 	}
350 
351 #define timeStamp(n) timeStamps[(n)]=(Option.printTotals ? clock():(clock_t)0)
352 	if ((! Option.filter) && (! Option.printLanguage))
353 		openTagFile ();
354 
355 	timeStamp (0);
356 
357 	if (! cArgOff (args))
358 	{
359 		verbose ("Reading command line arguments\n");
360 		resize = createTagsForArgs (args);
361 	}
362 	if (Option.fileList != NULL)
363 	{
364 		verbose ("Reading list file\n");
365 		resize = (bool) (createTagsFromListFile (Option.fileList) || resize);
366 	}
367 	if (Option.filter)
368 	{
369 		verbose ("Reading filter input\n");
370 		resize = (bool) (createTagsFromFileInput (stdin, true) || resize);
371 	}
372 	if (! files  &&  Option.recurse)
373 		resize = recurseIntoDirectory (".");
374 
375 	timeStamp (1);
376 
377 	if ((! Option.filter) && (!Option.printLanguage))
378 		closeTagFile (resize);
379 
380 	timeStamp (2);
381 
382 	if (Option.printTotals)
383 	{
384 		printTotals (timeStamps, Option.append, Option.sorted);
385 		if (Option.printTotals > 1)
386 			for (unsigned int i = 0; i < countParsers(); i++)
387 				printParserStatisticsIfUsed (i);
388 	}
389 
390 #undef timeStamp
391 }
392 
393 #ifdef HAVE_JANSSON
interactiveLoop(cookedArgs * args CTAGS_ATTR_UNUSED,void * user)394 void interactiveLoop (cookedArgs *args CTAGS_ATTR_UNUSED, void *user)
395 {
396 	struct interactiveModeArgs *iargs = user;
397 
398 	if (iargs->sandbox) {
399 		/* As of jansson 2.6, the object hashing is seeded off
400 		   of /dev/urandom, so trigger the hash seeding
401 		   before installing the syscall filter.
402 		*/
403 		json_t * tmp = json_object ();
404 		json_decref (tmp);
405 
406 		if (installSyscallFilter ()) {
407 			error (FATAL, "install_syscall_filter failed");
408 			/* The explicit exit call is needed because
409 			   "error (FATAL,..." just prints a message in
410 			   interactive mode. */
411 			exit (1);
412 		}
413 	}
414 
415 	char buffer[1024];
416 	json_t *request;
417 
418 	fputs ("{\"_type\": \"program\", \"name\": \"" PROGRAM_NAME "\", \"version\": \"" PROGRAM_VERSION "\"}\n", stdout);
419 	fflush (stdout);
420 
421 	while (fgets (buffer, sizeof(buffer), stdin))
422 	{
423 		if (buffer[0] == '\n')
424 			continue;
425 
426 		request = json_loads (buffer, JSON_DISABLE_EOF_CHECK, NULL);
427 		if (! request)
428 		{
429 			error (FATAL, "invalid json");
430 			goto next;
431 		}
432 
433 		json_t *command = json_object_get (request, "command");
434 		if (! command)
435 		{
436 			error (FATAL, "command name not found");
437 			goto next;
438 		}
439 
440 		if (!strcmp ("generate-tags", json_string_value (command)))
441 		{
442 			json_int_t size = -1;
443 			const char *filename;
444 
445 			if (json_unpack (request, "{ss}", "filename", &filename) == -1)
446 			{
447 				error (FATAL, "invalid generate-tags request");
448 				goto next;
449 			}
450 
451 			json_unpack (request, "{sI}", "size", &size);
452 
453 			openTagFile ();
454 			if (size == -1)
455 			{					/* read from disk */
456 				if (iargs->sandbox) {
457 					error (FATAL,
458 						   "invalid request in sandbox submode: reading file contents from a file is limited");
459 					closeTagFile (false);
460 					goto next;
461 				}
462 
463 				createTagsForEntry (filename);
464 			}
465 			else
466 			{					/* read nbytes from stream */
467 				unsigned char *data = eMalloc (size);
468 				size = fread (data, 1, size, stdin);
469 				MIO *mio = mio_new_memory (data, size, eRealloc, eFreeNoNullCheck);
470 				parseFileWithMio (filename, mio, NULL);
471 				mio_unref (mio);
472 			}
473 
474 			closeTagFile (false);
475 			fputs ("{\"_type\": \"completed\", \"command\": \"generate-tags\"}\n", stdout);
476 			fflush(stdout);
477 		}
478 		else
479 		{
480 			error (FATAL, "unknown command name");
481 			goto next;
482 		}
483 
484 	next:
485 		json_decref (request);
486 	}
487 }
488 #endif
489 
isSafeVar(const char * var)490 static bool isSafeVar (const char* var)
491 {
492 	const char *safe_vars[] = {
493 		"BASH_FUNC_module()=",
494 		"BASH_FUNC_scl()=",
495 		NULL
496 	};
497 	const char *sv;
498 
499 	for (sv = safe_vars[0]; sv != NULL; sv++)
500 		if (strncmp(var, sv, strlen (sv)) == 0)
501 			return true;
502 
503 	return false;
504 }
505 
sanitizeEnviron(void)506 static void sanitizeEnviron (void)
507 {
508 	char **e;
509 	int i;
510 
511 #if HAVE_DECL___ENVIRON
512 	e = __environ;
513 #elif HAVE_DECL__NSGETENVIRON
514 	{
515 		char ***ep = _NSGetEnviron();
516 		if (ep)
517 			e = *ep;
518 		else
519 			e = NULL;
520 	}
521 #else
522 	e = NULL;
523 #endif
524 
525 	if (!e)
526 		return;
527 
528 	for (i = 0; e [i]; i++)
529 	{
530 		char *value;
531 
532 		value = strchr (e [i], '=');
533 		if (!value)
534 			continue;
535 
536 		value++;
537 		if (!strncmp (value, "() {", 4))
538 		{
539 			if (isSafeVar (e [i]))
540 				continue;
541 			error (WARNING, "reset environment: %s", e [i]);
542 			value [0] = '\0';
543 		}
544 	}
545 }
546 
547 /*
548  *		Start up code
549  */
550 
ctags_cli_main(int argc CTAGS_ATTR_UNUSED,char ** argv)551 extern int ctags_cli_main (int argc CTAGS_ATTR_UNUSED, char **argv)
552 {
553 	cookedArgs *args;
554 
555 #if defined(WIN32) && defined(HAVE_MKSTEMP)
556 	/* MinGW-w64's mkstemp() uses rand() for generating temporary files. */
557 	srand ((unsigned int) clock ());
558 #endif
559 
560 	initDefaultTrashBox ();
561 
562 	DEBUG_INIT();
563 
564 	setErrorPrinter (stderrDefaultErrorPrinter, NULL);
565 	setMainLoop (batchMakeTags, NULL);
566 	setTagWriter (WRITER_U_CTAGS, NULL);
567 
568 	setCurrentDirectory ();
569 	setExecutableName (*argv++);
570 	sanitizeEnviron ();
571 	checkRegex ();
572 	initFieldObjects ();
573 	initXtagObjects ();
574 
575 	args = cArgNewFromArgv (argv);
576 	previewFirstOption (args);
577 	initializeParsing ();
578 	testEtagsInvocation ();
579 	initOptions ();
580 	initRegexOptscript ();
581 	readOptionConfiguration ();
582 	verbose ("Reading initial options from command line\n");
583 	parseCmdlineOptions (args);
584 	checkOptions ();
585 
586 	runMainLoop (args);
587 
588 	/*  Clean up.
589 	 */
590 	cArgDelete (args);
591 	freeKeywordTable ();
592 	freeRoutineResources ();
593 	freeInputFileResources ();
594 	freeTagFileResources ();
595 	freeOptionResources ();
596 	freeParserResources ();
597 	freeRegexResources ();
598 #ifdef HAVE_ICONV
599 	freeEncodingResources ();
600 #endif
601 
602 	finiDefaultTrashBox();
603 
604 	if (Option.printLanguage)
605 		return (Option.printLanguage == true)? 0: 1;
606 
607 	exit (0);
608 	return 0;
609 }
610