1 /*
2 *   Copyright (c) 1996-2003, Darren Hiebert
3 *
4 *   This source code is released into the public domain.
5 *
6 *   This module contains functions for reading tag files.
7 */
8 
9 #include "readtags.h"
10 #include "printtags.h"
11 #include <string.h>		/* strerror */
12 #include <stdlib.h>		/* exit */
13 #include <stdio.h>		/* stderr */
14 
15 static const char *TagFileName = "tags";
16 static const char *ProgramName;
17 static int extensionFields;
18 static int SortOverride;
19 static sortType SortMethod;
20 static int allowPrintLineNumber;
21 static int debugMode;
22 static int escaping;
23 #ifdef READTAGS_DSL
24 #include "dsl/qualifier.h"
25 static QCode *Qualifier;
26 #include "dsl/sorter.h"
27 static SCode *Sorter;
28 #endif
29 
tagsStrerror(int err)30 static const char* tagsStrerror (int err)
31 {
32 	if (err > 0)
33 		return strerror (err);
34 	else if (err < 0)
35 	{
36 		switch (err)
37 		{
38 		case TagErrnoUnexpectedSortedMethod:
39 			return "Unexpected sorted method";
40 		case TagErrnoUnexpectedFormat:
41 			return "Unexpected format number";
42 		case TagErrnoUnexpectedLineno:
43 			return "Unexpected value for line: field";
44 		case TagErrnoInvalidArgument:
45 			return "Unexpected argument passed to the API function";
46 		default:
47 			return "Unknown error";
48 		}
49 	}
50 	else
51 		return "no error";
52 }
53 
printTag(const tagEntry * entry)54 static void printTag (const tagEntry *entry)
55 {
56 	tagPrintOptions opts = {
57 		.extensionFields = extensionFields,
58 		.lineNumber = allowPrintLineNumber,
59 		.escaping = escaping,
60 	};
61 	tagsPrint (entry, &opts, NULL, stdout);
62 }
63 
printPseudoTag(const tagEntry * entry)64 static void printPseudoTag (const tagEntry *entry)
65 {
66 	tagPrintOptions opts = {
67 		.extensionFields = extensionFields,
68 		.lineNumber = allowPrintLineNumber,
69 		.escaping = escaping,
70 	};
71 	tagsPrintPseudoTag (entry, &opts, NULL, stdout);
72 }
73 
74 #ifdef READTAGS_DSL
freeCopiedTag(tagEntry * e)75 static void freeCopiedTag (tagEntry *e)
76 {
77 	free ((void *)e->name);
78 	free ((void *)e->file);
79 	if (e->address.pattern)
80 		free ((void *)e->address.pattern);
81 	if (e->kind)
82 		free ((void *)e->kind);
83 	for (unsigned short c = 0; c < e->fields.count; c++)
84 	{
85 		free ((void *)e->fields.list[c].key);
86 		free ((void *)e->fields.list[c].value);
87 	}
88 	if (e->fields.count)
89 		free ((void *)e->fields.list);
90 	free ((void *)e);
91 }
92 
copyTag(tagEntry * o)93 static tagEntry *copyTag (tagEntry *o)
94 {
95 	tagEntry *n;
96 
97 	n = calloc (1, sizeof  (*o));
98 	if (!n)
99 		perror (__FUNCTION__);
100 
101 	n->name = strdup (o->name);
102 
103 	if (!n->name)
104 		perror (__FUNCTION__);
105 
106 	if (o->file)
107 		n->file = strdup (o->file);
108 	if (o->file && !n->file)
109 		perror (__FUNCTION__);
110 
111 	if (o->address.pattern)
112 	{
113 		n->address.pattern = strdup (o->address.pattern);
114 		if (!n->address.pattern)
115 			perror (__FUNCTION__);
116 	}
117 
118 	n->address.lineNumber = o->address.lineNumber;
119 
120 	if (o->kind)
121 	{
122 		n->kind = strdup (o->kind);
123 		if (!n->kind)
124 			perror (__FUNCTION__);
125 	}
126 
127 	n->fileScope = o->fileScope;
128 	n->fields.count = o->fields.count;
129 
130 	if (o->fields.count == 0)
131 		return n;
132 
133 	n->fields.list = malloc (o->fields.count *sizeof (*o->fields.list));
134 	if (!n->fields.list)
135 		perror (__FUNCTION__);
136 
137 	for (unsigned short c = 0; c < o->fields.count; c++)
138 	{
139 		n->fields.list[c].key = strdup (o->fields.list[c].key);
140 		if (!n->fields.list[c].key)
141 			perror (__FUNCTION__);
142 
143 		n->fields.list[c].value = strdup (o->fields.list[c].value);
144 		if (!n->fields.list[c].value)
145 			perror (__FUNCTION__);
146 	}
147 
148 	return n;
149 }
150 
151 struct tagEntryHolder {
152 	tagEntry *e;
153 };
154 struct tagEntryArray {
155 	int count;
156 	int length;
157 	struct tagEntryHolder *a;
158 };
159 
tagEntryArrayNew(void)160 struct tagEntryArray *tagEntryArrayNew (void)
161 {
162 	struct tagEntryArray * a = malloc (sizeof (struct tagEntryArray));
163 	if (!a)
164 		perror(__FUNCTION__);
165 
166 	a->count = 0;
167 	a->length = 1024;
168 	a->a = malloc(a->length * sizeof (a->a[0]));
169 	if (!a->a)
170 		perror(__FUNCTION__);
171 
172 	return a;
173 }
174 
tagEntryArrayPush(struct tagEntryArray * a,tagEntry * e)175 void tagEntryArrayPush (struct tagEntryArray *a, tagEntry *e)
176 {
177 	if (a->count + 1 == a->length)
178 	{
179 		if (a->length * 2 < a->length)
180 			perror("Too large array allocation");
181 
182 		struct tagEntryHolder *tmp = realloc (a->a, sizeof (a->a[0]) * (a->length * 2));
183 		if (!tmp)
184 			perror(__FUNCTION__);
185 
186 		a->a = tmp;
187 		a->length *= 2;
188 	}
189 
190 	a->a[a->count++].e = e;
191 }
192 
tagEntryArrayFree(struct tagEntryArray * a,int freeTags)193 void tagEntryArrayFree (struct tagEntryArray *a, int freeTags)
194 {
195 	if (freeTags)
196 	{
197 		for (int i = 0; i < a->count; i++)
198 			freeCopiedTag (a->a[i].e);
199 	}
200 	free (a->a);
201 	free (a);
202 }
203 
compareTagEntry(const void * a,const void * b)204 static int compareTagEntry (const void *a, const void *b)
205 {
206 	return s_compare (((struct tagEntryHolder *)a)->e, ((struct tagEntryHolder *)b)->e, Sorter);
207 }
208 
walkTags(tagFile * const file,tagEntry * first_entry,tagResult (* nextfn)(tagFile * const,tagEntry *),void (* actionfn)(const tagEntry *))209 static void walkTags (tagFile *const file, tagEntry *first_entry,
210 					  tagResult (* nextfn) (tagFile *const, tagEntry *),
211 					  void (* actionfn) (const tagEntry *))
212 {
213 	struct tagEntryArray *a = NULL;
214 
215 	if (Sorter)
216 		a = tagEntryArrayNew ();
217 
218 	do
219 	{
220 		if (Qualifier)
221 		{
222 			int i = q_is_acceptable (Qualifier, first_entry);
223 			switch (i)
224 			{
225 			case Q_REJECT:
226 				continue;
227 			case Q_ERROR:
228 				exit (1);
229 			}
230 		}
231 
232 		if (a)
233 		{
234 			tagEntry *e = copyTag (first_entry);
235 			tagEntryArrayPush (a, e);
236 		}
237 		else
238 			(* actionfn) (first_entry);
239 	} while ( (*nextfn) (file, first_entry) == TagSuccess);
240 
241 	int err = tagsGetErrno (file);
242 	if (err != 0)
243 	{
244 		fprintf (stderr, "%s: error in walktTags(): %s\n",
245 				 ProgramName,
246 				 tagsStrerror (err));
247 		exit (1);
248 	}
249 
250 	if (a)
251 	{
252 		qsort (a->a, a->count, sizeof (a->a[0]), compareTagEntry);
253 		for (int i = 0; i < a->count; i++)
254 			(* actionfn) (a->a[i].e);
255 		tagEntryArrayFree (a, 1);
256 	}
257 }
258 #else
walkTags(tagFile * const file,tagEntry * first_entry,tagResult (* nextfn)(tagFile * const,tagEntry *),void (* actionfn)(const tagEntry *))259 static void walkTags (tagFile *const file, tagEntry *first_entry,
260 					  tagResult (* nextfn) (tagFile *const, tagEntry *),
261 					  void (* actionfn) (const tagEntry *))
262 {
263 	do
264 		(* actionfn) (first_entry);
265 	while ( (*nextfn) (file, first_entry) == TagSuccess);
266 
267 	int err = tagsGetErrno (file);
268 	if (err != 0)
269 	{
270 		fprintf (stderr, "%s: error in walktTags(): %s\n",
271 				 ProgramName,
272 				 tagsStrerror (err));
273 		exit (1);
274 	}
275 }
276 #endif
277 
findTag(const char * const name,const int options)278 static void findTag (const char *const name, const int options)
279 {
280 	tagFileInfo info;
281 	tagEntry entry;
282 	tagFile *const file = tagsOpen (TagFileName, &info);
283 	if (file == NULL || !info.status.opened)
284 	{
285 		fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
286 				 ProgramName, tagsStrerror (info.status.error_number), TagFileName);
287 		if (file)
288 			tagsClose (file);
289 		exit (1);
290 	}
291 	else
292 	{
293 		int err = 0;
294 		if (SortOverride)
295 		{
296 			if (tagsSetSortType (file, SortMethod) != TagSuccess)
297 			{
298 				err = tagsGetErrno (file);
299 				fprintf (stderr, "%s: cannot set sort type to %d: %s\n",
300 						 ProgramName,
301 						 SortMethod,
302 						 tagsStrerror (err));
303 				exit (1);
304 			}
305 		}
306 		if (debugMode)
307 			fprintf (stderr, "%s: searching for \"%s\" in \"%s\"\n",
308 					 ProgramName, name, TagFileName);
309 		if (tagsFind (file, &entry, name, options) == TagSuccess)
310 			walkTags (file, &entry, tagsFindNext, printTag);
311 		else if ((err = tagsGetErrno (file)) != 0)
312 		{
313 			fprintf (stderr, "%s: error in tagsFind(): %s\n",
314 					 ProgramName,
315 					 tagsStrerror (err));
316 			exit (1);
317 		}
318 		tagsClose (file);
319 	}
320 }
321 
listTags(int pseudoTags)322 static void listTags (int pseudoTags)
323 {
324 	tagFileInfo info;
325 	tagEntry entry;
326 	tagFile *const file = tagsOpen (TagFileName, &info);
327 	if (file == NULL || !info.status.opened)
328 	{
329 		fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
330 				 ProgramName,
331 				 tagsStrerror (info.status.error_number),
332 				 TagFileName);
333 		if (file)
334 			tagsClose (file);
335 		exit (1);
336 	}
337 	else if (pseudoTags)
338 	{
339 		int err = 0;
340 		if (tagsFirstPseudoTag (file, &entry) == TagSuccess)
341 			walkTags (file, &entry, tagsNextPseudoTag, printPseudoTag);
342 		else if ((err = tagsGetErrno (file)) != 0)
343 		{
344 			fprintf (stderr, "%s: error in tagsFirstPseudoTag(): %s\n",
345 					 ProgramName,
346 					 tagsStrerror (err));
347 			exit (1);
348 		}
349 		tagsClose (file);
350 	}
351 	else
352 	{
353 		int err = 0;
354 		if (tagsFirst (file, &entry) == TagSuccess)
355 			walkTags (file, &entry, tagsNext, printTag);
356 		else if ((err = tagsGetErrno (file)) != 0)
357 		{
358 			fprintf (stderr, "%s: error in tagsFirst(): %s\n",
359 					 ProgramName,
360 					 tagsStrerror (err));
361 			exit (1);
362 		}
363 		tagsClose (file);
364 	}
365 }
366 
367 static const char *const Usage =
368 	"Find tag file entries matching specified names.\n\n"
369 	"Usage: \n"
370 	"    %s -h | --help\n"
371 	"        Print this help message.\n"
372 #ifdef READTAGS_DSL
373 	"    %s -H POSTPROCESSOR | --help-expression POSTPROCESSOR\n"
374 	"        Print available terms that can be used in POSTPROCESSOR expression.\n"
375 	"        POSTPROCESSOR: filter sorter\n"
376 #endif
377 	"    %s [OPTIONS] ACTION\n"
378 	"        Do the specified action.\n"
379 	"Actions:\n"
380 	"    -l | --list\n"
381 	"        List regular tags.\n"
382 	"    [-] NAME...\n"
383 	"        List regular tags matching NAME(s).\n"
384 	"        \"-\" indicates arguments after this as NAME(s) even if they start with -.\n"
385 	"    -D | --list-pseudo-tags\n"
386 	"        List pseudo tags.\n"
387 	"Options:\n"
388 	"    -d | --debug\n"
389 	"        Turn on debugging output.\n"
390 	"    -E | --escape-output\n"
391 	"        Escape characters like tabs in output as described in tags(5).\n"
392 	"    -e | --extension-fields\n"
393 	"        Include extension fields in output.\n"
394 	"    -i | --icase-match\n"
395 	"        Perform case-insensitive matching in the NAME action.\n"
396 	"    -n | --line-number\n"
397 	"        Also include the line number field when -e option is given.\n"
398 	"    -p | --prefix-match\n"
399 	"        Perform prefix matching in the NAME action.\n"
400 	"    -t TAGFILE | --tag-file TAGFILE\n"
401 	"        Use specified tag file (default: \"tags\").\n"
402 	"    -s[0|1|2] | --override-sort-detection METHOD\n"
403 	"        Override sort detection of tag file.\n"
404 	"        METHOD: unsorted|sorted|foldcase\n"
405 #ifdef READTAGS_DSL
406 	"    -Q EXP | --filter EXP\n"
407 	"        Filter the tags listed by ACTION with EXP before printing.\n"
408 	"    -S EXP | --sorter EXP\n"
409 	"        Sort the tags listed by ACTION with EXP before printing.\n"
410 #endif
411 	;
412 
printUsage(FILE * stream,int exitCode)413 static void printUsage(FILE* stream, int exitCode)
414 {
415 	fprintf (stream, Usage, ProgramName,
416 #ifdef READTAGS_DSL
417 			 ProgramName,
418 #endif
419 			 ProgramName);
420 	exit (exitCode);
421 }
422 
423 #ifdef READTAGS_DSL
printFilterExpression(FILE * stream,int exitCode)424 static void printFilterExpression (FILE *stream, int exitCode)
425 {
426 	fprintf (stream, "Filter expression: \n");
427 	q_help (stream);
428 	exit (exitCode);
429 }
430 
printSorterExpression(FILE * stream,int exitCode)431 static void printSorterExpression (FILE *stream, int exitCode)
432 {
433 	fprintf (stream, "Sorter expression: \n");
434 	s_help (stream);
435 	exit (exitCode);
436 }
437 
compileExpression(const char * exp,void * (* compiler)(EsObject *),const char * compiler_name)438 static void *compileExpression(const char* exp, void * (*compiler) (EsObject *),
439 							   const char *compiler_name)
440 {
441 	EsObject *sexp = es_read_from_string (exp, NULL);
442 	void *code;
443 
444 	if (es_error_p (sexp))
445 	{
446 		fprintf (stderr,
447 				 "Failed to read the expression for %s: %s\n", compiler_name, exp);
448 		fprintf (stderr,
449 				 "Reason: %s\n", es_error_name (sexp));
450 		exit (1);
451 	}
452 
453 	code = compiler (sexp);
454 	if (code == NULL)
455 	{
456 		fprintf (stderr,
457 				 "Failed to compile the expression of %s: %s\n", compiler_name, exp);
458 		exit (1);
459 	}
460 	es_object_unref (sexp);
461 	return code;
462 }
463 #endif
464 
main(int argc,char ** argv)465 extern int main (int argc, char **argv)
466 {
467 	int options = 0;
468 	int actionSupplied = 0;
469 	int i;
470 	int ignore_prefix = 0;
471 
472 	ProgramName = argv [0];
473 	if (argc == 1)
474 		printUsage(stderr, 1);
475 	for (i = 1  ;  i < argc  ;  ++i)
476 	{
477 		const char *const arg = argv [i];
478 		if (ignore_prefix || arg [0] != '-')
479 		{
480 			findTag (arg, options);
481 			actionSupplied = 1;
482 		}
483 		else if (arg [0] == '-' && arg [1] == '\0')
484 			ignore_prefix = 1;
485 		else if (arg [0] == '-' && arg [1] == '-')
486 		{
487 			const char *optname = arg + 2;
488 			if (strcmp (optname, "debug") == 0)
489 				debugMode++;
490 			else if (strcmp (optname, "list-pseudo-tags") == 0)
491 			{
492 				listTags (1);
493 				actionSupplied = 1;
494 			}
495 			else if (strcmp (optname, "help") == 0)
496 				printUsage (stdout, 0);
497 #ifdef READTAGS_DSL
498 			else if (strcmp (optname, "help-expression") == 0)
499 			{
500 				if (i + 1 < argc)
501 				{
502 					const char *exp_klass = argv [++i];
503 					if (strcmp (exp_klass, "filter") == 0)
504 						printFilterExpression (stdout, 0);
505 					if (strcmp (exp_klass, "sorter") == 0)
506 						printSorterExpression (stdout, 0);
507 					else
508 					{
509 						fprintf (stderr, "%s: unknown expression class for --%s option\n",
510 								 ProgramName, optname);
511 						exit (1);
512 
513 					}
514 				}
515 				else
516 				{
517 					fprintf (stderr, "%s: missing expression class for --%s option\n",
518 							 ProgramName, optname);
519 					exit (1);
520 				}
521 			}
522 #endif
523 			else if (strcmp (optname, "escape-output") == 0)
524 				escaping = 1;
525 			else if (strcmp (optname, "extension-fields") == 0)
526 				extensionFields = 1;
527 			else if (strcmp (optname, "icase-match") == 0)
528 				options |= TAG_IGNORECASE;
529 			else if (strcmp (optname, "prefix-match") == 0)
530 				options |= TAG_PARTIALMATCH;
531 			else if (strcmp (optname, "list") == 0)
532 			{
533 				listTags (0);
534 				actionSupplied = 1;
535 			}
536 			else if (strcmp (optname, "line-number") == 0)
537 				allowPrintLineNumber = 1;
538 			else if (strcmp (optname, "tag-file") == 0)
539 			{
540 				if (i + 1 < argc)
541 					TagFileName = argv [++i];
542 				else
543 					printUsage (stderr, 1);
544 			}
545 			else if (strcmp (optname, "override-sort-detection") == 0)
546 			{
547 				if (i + 1 < argc)
548 				{
549 					const char *sort_spec = argv [++i];
550 					if (strcmp (sort_spec, "0") == 0
551 						|| strcmp (sort_spec, "unsorted") == 0)
552 						SortMethod = 0;
553 					else if (strcmp (sort_spec, "1") == 0
554 							 || strcmp (sort_spec, "sorted") == 0)
555 						SortMethod = 1;
556 					else if (strcmp (sort_spec, "2") == 0
557 							 || strcmp (sort_spec, "foldcase") == 0)
558 						SortMethod = 2;
559 					else
560 					{
561 						fprintf (stderr, "%s: unknown sort method for --%s option\n",
562 								 ProgramName, optname);
563 						exit (1);
564 					}
565 				}
566 				else
567 				{
568 					fprintf (stderr, "%s: missing sort method for --%s option\n",
569 							 ProgramName, optname);
570 					exit (1);
571 				}
572 			}
573 #ifdef READTAGS_DSL
574 			else if (strcmp (optname, "filter") == 0)
575 			{
576 				if (i + 1 < argc)
577 					Qualifier = compileExpression (argv[++i],
578 												   (void * (*)(EsObject *))q_compile,
579 												   optname);
580 				else
581 				{
582 					fprintf (stderr, "%s: missing filter expression for --%s option\n",
583 							 ProgramName, optname);
584 					exit (1);
585 				}
586 			}
587 			else if (strcmp (optname, "sorter") == 0)
588 			{
589 				if (i + 1 < argc)
590 					Sorter = compileExpression (argv[++i],
591 												(void * (*)(EsObject *))s_compile,
592 												optname);
593 				else
594 				{
595 					fprintf (stderr, "%s: missing sorter expression for --%s option\n",
596 							 ProgramName, optname);
597 					exit (1);
598 				}
599 			}
600 #endif
601 			else
602 			{
603 				fprintf (stderr, "%s: unknown long options: --%s\n",
604 						 ProgramName, optname);
605 				exit (1);
606 				break;
607 			}
608 		}
609 		else
610 		{
611 			size_t j;
612 			for (j = 1  ;  arg [j] != '\0'  ;  ++j)
613 			{
614 				switch (arg [j])
615 				{
616 					case 'd': debugMode++; break;
617 					case 'D': listTags (1); actionSupplied = 1; break;
618 					case 'h': printUsage (stdout, 0); break;
619 #ifdef READTAGS_DSL
620 					case 'H':
621 						if (i + 1 < argc)
622 						{
623 							const char *exp_klass = argv [++i];
624 							if (strcmp (exp_klass, "filter") == 0)
625 								printFilterExpression (stdout, 0);
626 							else if (strcmp (exp_klass, "sorter") == 0)
627 								printSorterExpression (stdout, 0);
628 							else
629 								printUsage(stderr, 1);
630 						}
631 						else
632 							printUsage(stderr, 1);
633 #endif
634 					case 'E': escaping = 1; break;
635 					case 'e': extensionFields = 1;         break;
636 					case 'i': options |= TAG_IGNORECASE;   break;
637 					case 'p': options |= TAG_PARTIALMATCH; break;
638 					case 'l': listTags (0); actionSupplied = 1; break;
639 					case 'n': allowPrintLineNumber = 1; break;
640 					case 't':
641 						if (arg [j+1] != '\0')
642 						{
643 							TagFileName = arg + j + 1;
644 							j += strlen (TagFileName);
645 						}
646 						else if (i + 1 < argc)
647 							TagFileName = argv [++i];
648 						else
649 							printUsage(stderr, 1);
650 						break;
651 					case 's':
652 						SortOverride = 1;
653 						++j;
654 						if (arg [j] == '\0')
655 							SortMethod = TAG_SORTED;
656 						else if (strchr ("012", arg[j]) != NULL)
657 							SortMethod = (sortType) (arg[j] - '0');
658 						else
659 							printUsage(stderr, 1);
660 						break;
661 #ifdef READTAGS_DSL
662 					case 'Q':
663 						if (i + 1 == argc)
664 							printUsage(stderr, 1);
665 						Qualifier = compileExpression (argv[++i],
666 													   (void * (*)(EsObject *))q_compile,
667 													   "filter");
668 						break;
669 					case 'S':
670 						if (i + 1 == argc)
671 							printUsage(stderr, 1);
672 						Sorter = compileExpression (argv[++i],
673 													   (void * (*)(EsObject *))s_compile,
674 													   "sorter");
675 						break;
676 #endif
677 					default:
678 						fprintf (stderr, "%s: unknown option: %c\n",
679 									ProgramName, arg[j]);
680 						exit (1);
681 						break;
682 				}
683 			}
684 		}
685 	}
686 	if (! actionSupplied)
687 	{
688 		fprintf (stderr,
689 			"%s: no action specified: specify one of NAME, -l or -D\n",
690 			ProgramName);
691 		exit (1);
692 	}
693 #ifdef READTAGS_DSL
694 	if (Qualifier)
695 		q_destroy (Qualifier);
696 	if (Sorter)
697 		s_destroy (Sorter);
698 #endif
699 	return 0;
700 }
701