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