xref: /Universal-ctags/libreadtags/readtags.c (revision d5b81c4aa18f31534f9ed152bfb0988c340b28ff)
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 /*
10 *   INCLUDE FILES
11 */
12 #include <stdlib.h>
13 #include <string.h>
14 #include <ctype.h>
15 #include <stdio.h>
16 #include <errno.h>
17 #include <sys/types.h>  /* to declare off_t */
18 
19 #include "readtags.h"
20 
21 /*
22 *   MACROS
23 */
24 #define TAB '\t'
25 
26 
27 /*
28 *   DATA DECLARATIONS
29 */
30 typedef struct {
31 	size_t size;
32 	char *buffer;
33 } vstring;
34 
35 /* Define readtags' own off_t. */
36 #ifdef _WIN32
37 typedef long long rt_off_t;
38 #else
39 typedef off_t rt_off_t;
40 #endif
41 
42 /* Information about current tag file */
43 struct sTagFile {
44 		/* has the file been opened and this structure initialized? */
45 	short initialized;
46 		/* format of tag file */
47 	short format;
48 		/* how is the tag file sorted? */
49 	tagSortType sortMethod;
50 		/* pointer to file structure */
51 	FILE* fp;
52 		/* file position of first character of `line' */
53 	rt_off_t pos;
54 		/* size of tag file in seekable positions */
55 	rt_off_t size;
56 		/* last line read */
57 	vstring line;
58 		/* name of tag in last line read */
59 	vstring name;
60 		/* defines tag search state */
61 	struct {
62 				/* file position of last match for tag */
63 			rt_off_t pos;
64 				/* name of tag last searched for */
65 			char *name;
66 				/* length of name for partial matches */
67 			size_t nameLength;
68 				/* performing partial match */
69 			short partial;
70 				/* ignoring case */
71 			short ignorecase;
72 	} search;
73 		/* miscellaneous extension fields */
74 	struct {
75 				/* number of entries in `list' */
76 			unsigned short max;
77 				/* list of key value pairs */
78 			tagExtensionField *list;
79 	} fields;
80 		/* buffers to be freed at close */
81 	struct {
82 			/* name of program author */
83 		char *author;
84 			/* name of program */
85 		char *name;
86 			/* URL of distribution */
87 		char *url;
88 			/* program version */
89 		char *version;
90 	} program;
91 		/* 0 (initial state set by calloc), errno value,
92 		 * or tagErrno typed value */
93 	int err;
94 };
95 
96 /*
97 *   DATA DEFINITIONS
98 */
99 static const char *const EmptyString = "";
100 static const char *const PseudoTagPrefix = "!_";
101 static const size_t PseudoTagPrefixLength = 2;
102 
103 /*
104 *   FUNCTION DEFINITIONS
105 */
106 
readtags_ftell(FILE * fp)107 static rt_off_t readtags_ftell(FILE *fp)
108 {
109 	rt_off_t pos;
110 
111 #ifdef _WIN32
112 	pos = _ftelli64(fp);
113 #else
114 	pos = ftell(fp);
115 #endif
116 
117 	return pos;
118 }
119 
readtags_fseek(FILE * fp,rt_off_t pos,int whence)120 static int readtags_fseek(FILE *fp, rt_off_t pos, int whence)
121 {
122 	int ret;
123 
124 #ifdef _WIN32
125 	ret = _fseeki64(fp, pos, whence);
126 #else
127 	ret = fseek(fp, pos, whence);
128 #endif
129 
130 	return ret;
131 }
132 
133 /* Converts a hexadecimal digit to its value */
xdigitValue(char digit)134 static int xdigitValue (char digit)
135 {
136 	if (digit >= '0' && digit <= '9')
137 		return digit - '0';
138 	else if (digit >= 'a' && digit <= 'f')
139 		return 10 + digit - 'a';
140 	else if (digit >= 'A' && digit <= 'F')
141 		return 10 + digit - 'A';
142 	else
143 		return 0;
144 }
145 
146 /*
147  * Reads the first character from the string, possibly un-escaping it, and
148  * advances *s to the start of the next character.
149  */
readTagCharacter(const char ** s)150 static int readTagCharacter (const char **s)
151 {
152 	int c = **(const unsigned char **)s;
153 
154 	(*s)++;
155 
156 	if (c == '\\')
157 	{
158 		switch (**s)
159 		{
160 			case 't': c = '\t'; (*s)++; break;
161 			case 'r': c = '\r'; (*s)++; break;
162 			case 'n': c = '\n'; (*s)++; break;
163 			case '\\': c = '\\'; (*s)++; break;
164 			/* Universal-CTags extensions */
165 			case 'a': c = '\a'; (*s)++; break;
166 			case 'b': c = '\b'; (*s)++; break;
167 			case 'v': c = '\v'; (*s)++; break;
168 			case 'f': c = '\f'; (*s)++; break;
169 			case 'x':
170 				if (isxdigit ((*s)[1]) && isxdigit ((*s)[2]))
171 				{
172 					int val = (xdigitValue ((*s)[1]) << 4) | xdigitValue ((*s)[2]);
173 					if (val < 0x80)
174 					{
175 						(*s) += 3;
176 						c = val;
177 					}
178 				}
179 				break;
180 		}
181 	}
182 
183 	return c;
184 }
185 
186 /*
187  * Compare two strings, ignoring case.
188  * Return 0 for match, < 0 for smaller, > 0 for bigger
189  * Make sure case is folded to uppercase in comparison (like for 'sort -f')
190  * This makes a difference when one of the chars lies between upper and lower
191  * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
192  */
taguppercmp(const char * s1,const char * s2)193 static int taguppercmp (const char *s1, const char *s2)
194 {
195 	int result;
196 	int c1, c2;
197 	do
198 	{
199 		c1 = (unsigned char)*s1++;
200 		c2 = readTagCharacter (&s2);
201 
202 		result = toupper (c1) - toupper (c2);
203 	} while (result == 0  &&  c1 != '\0'  &&  c2 != '\0');
204 	return result;
205 }
206 
tagnuppercmp(const char * s1,const char * s2,size_t n)207 static int tagnuppercmp (const char *s1, const char *s2, size_t n)
208 {
209 	int result;
210 	int c1, c2;
211 	do
212 	{
213 		c1 = (unsigned char)*s1++;
214 		c2 = readTagCharacter (&s2);
215 
216 		result = toupper (c1) - toupper (c2);
217 	} while (result == 0  &&  --n > 0  &&  c1 != '\0'  &&  c2 != '\0');
218 	return result;
219 }
220 
tagcmp(const char * s1,const char * s2)221 static int tagcmp (const char *s1, const char *s2)
222 {
223 	int result;
224 	int c1, c2;
225 	do
226 	{
227 		c1 = (unsigned char)*s1++;
228 		c2 = readTagCharacter (&s2);
229 
230 		result = c1 - c2;
231 	} while (result == 0  &&  c1 != '\0'  &&  c2 != '\0');
232 	return result;
233 }
234 
tagncmp(const char * s1,const char * s2,size_t n)235 static int tagncmp (const char *s1, const char *s2, size_t n)
236 {
237 	int result;
238 	int c1, c2;
239 	do
240 	{
241 		c1 = *s1++;
242 		c2 = readTagCharacter (&s2);
243 
244 		result = c1 - c2;
245 	} while (result == 0  &&  --n > 0  &&  c1 != '\0'  &&  c2 != '\0');
246 	return result;
247 }
248 
growString(vstring * s)249 static tagResult growString (vstring *s)
250 {
251 	tagResult result = TagFailure;
252 	size_t newLength;
253 	char *newLine;
254 	if (s->size == 0)
255 	{
256 		newLength = 128;
257 		newLine = (char*) malloc (newLength);
258 		if (newLine)
259 			*newLine = '\0';
260 	}
261 	else
262 	{
263 		newLength = 2 * s->size;
264 		newLine = (char*) realloc (s->buffer, newLength);
265 	}
266 	if (newLine == NULL)
267 		perror ("string too large");
268 	else
269 	{
270 		s->buffer = newLine;
271 		s->size = newLength;
272 		result = TagSuccess;
273 	}
274 	return result;
275 }
276 
277 /* Copy name of tag out of tag line */
copyName(tagFile * const file)278 static tagResult copyName (tagFile *const file)
279 {
280 	size_t length;
281 	const char *end = strchr (file->line.buffer, '\t');
282 	if (end == NULL)
283 	{
284 		end = strchr (file->line.buffer, '\n');
285 		if (end == NULL)
286 			end = strchr (file->line.buffer, '\r');
287 	}
288 	if (end != NULL)
289 		length = end - file->line.buffer;
290 	else
291 		length = strlen (file->line.buffer);
292 	while (length >= file->name.size)
293 	{
294 		if (growString (&file->name) != TagSuccess)
295 			return TagFailure;
296 	}
297 	strncpy (file->name.buffer, file->line.buffer, length);
298 	file->name.buffer [length] = '\0';
299 	return TagSuccess;
300 }
301 
302 /* Return 1 on success.
303  * Return 0 on failure or EOF.
304  * errno is set to *err unless EOF.
305  */
readTagLineRaw(tagFile * const file,int * err)306 static int readTagLineRaw (tagFile *const file, int *err)
307 {
308 	int result = 1;
309 	int reReadLine;
310 
311 	/*  If reading the line places any character other than a null or a
312 	 *  newline at the last character position in the buffer (one less than
313 	 *  the buffer size), then we must resize the buffer and reattempt to read
314 	 *  the line.
315 	 */
316 	do
317 	{
318 		char *const pLastChar = file->line.buffer + file->line.size - 2;
319 		char *line;
320 
321 		file->pos = readtags_ftell (file->fp);
322 		if (file->pos < 0)
323 		{
324 			*err = errno;
325 			result = 0;
326 			break;
327 		}
328 		reReadLine = 0;
329 		*pLastChar = '\0';
330 		line = fgets (file->line.buffer, (int) file->line.size, file->fp);
331 		if (line == NULL)
332 		{
333 			/* read error */
334 			*err = 0;
335 			if (! feof (file->fp))
336 				*err = errno;
337 			result = 0;
338 		}
339 		else if (*pLastChar != '\0'  &&
340 					*pLastChar != '\n'  &&  *pLastChar != '\r')
341 		{
342 			/*  buffer overflow */
343 			if (growString (&file->line) != TagSuccess)
344 			{
345 				*err = ENOMEM;
346 				result = 0;
347 			}
348 
349 			if (readtags_fseek (file->fp, file->pos, SEEK_SET) < 0)
350 			{
351 				*err = errno;
352 				result = 0;
353 			}
354 			reReadLine = 1;
355 		}
356 		else
357 		{
358 			size_t i = strlen (file->line.buffer);
359 			while (i > 0  &&
360 				   (file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r'))
361 			{
362 				file->line.buffer [i - 1] = '\0';
363 				--i;
364 			}
365 		}
366 	} while (reReadLine  &&  result);
367 	if (result)
368 	{
369 		if (copyName (file) != TagSuccess)
370 		{
371 			*err = ENOMEM;
372 			result = 0;
373 		}
374 	}
375 	return result;
376 }
377 
378 /* Return 1 on success.
379  * Return 0 on failure or EOF.
380  * errno is set to *err unless EOF.
381  */
readTagLine(tagFile * const file,int * err)382 static int readTagLine (tagFile *const file, int *err)
383 {
384 	int result;
385 	do
386 	{
387 		result = readTagLineRaw (file, err);
388 	} while (result && *file->name.buffer == '\0');
389 	return result;
390 }
391 
growFields(tagFile * const file)392 static tagResult growFields (tagFile *const file)
393 {
394 	tagResult result = TagFailure;
395 	unsigned short newCount = (unsigned short) 2 * file->fields.max;
396 	tagExtensionField *newFields = (tagExtensionField*)
397 			realloc (file->fields.list, newCount * sizeof (tagExtensionField));
398 	if (newFields == NULL)
399 		perror ("too many extension fields");
400 	else
401 	{
402 		file->fields.list = newFields;
403 		file->fields.max = newCount;
404 		result = TagSuccess;
405 	}
406 	return result;
407 }
408 
parseExtensionFields(tagFile * const file,tagEntry * const entry,char * const string,int * err)409 static tagResult parseExtensionFields (tagFile *const file, tagEntry *const entry,
410 									   char *const string, int *err)
411 {
412 	char *p = string;
413 	char *tail = string + (string? strlen(string):0);
414 	size_t q_len;
415 
416 	while (p != NULL  &&  *p != '\0')
417 	{
418 		while (*p == TAB)
419 			*p++ = '\0';
420 		if (*p != '\0')
421 		{
422 			char *colon;
423 			char *field = p;
424 			p = strchr (p, TAB);
425 			if (p != NULL)
426 				*p++ = '\0';
427 			colon = strchr (field, ':');
428 			if (colon == NULL)
429 				entry->kind = field;
430 			else
431 			{
432 				const char *key = field;
433 				char *q = colon + 1;
434 				const char *value = q;
435 				const int key_len = colon - key;
436 				*colon = '\0';
437 
438 				q_len = tail - q;
439 
440 				/* Unescaping */
441 				while (*q != '\0')
442 				{
443 					const char *next = q;
444 					int ch = readTagCharacter (&next);
445 					size_t skip = next - q;
446 
447 					*q = (char) ch;
448 					q++;
449 					q_len -= skip;
450 					if (skip > 1)
451 					{
452 						/* + 1 is for moving the area including the last '\0'. */
453 						memmove (q, next, q_len + 1);
454 						if (p)
455 							p -= skip - 1;
456 						if (tail != string)
457 							tail -= skip - 1;
458 					}
459 				}
460 
461 				if (key_len == 4)
462 				{
463 					if (memcmp (key, "kind", 4) == 0)
464 						entry->kind = value;
465 					else if (memcmp (key, "file", 4) == 0)
466 						entry->fileScope = 1;
467 					else if (memcmp (key, "line", 4) == 0)
468 					{
469 						char *endptr = NULL;
470 						long m = strtol (value, &endptr, 10);
471 						if (*endptr != '\0' || m < 0)
472 						{
473 							*err = TagErrnoUnexpectedLineno;
474 							return TagFailure;
475 						}
476 						entry->address.lineNumber = m;
477 					}
478 					else
479 						goto normalField;
480 				}
481 				else
482 				{
483 				normalField:
484 					if (entry->fields.count == file->fields.max)
485 					{
486 						if (growFields (file) != TagSuccess)
487 						{
488 							*err = ENOMEM;
489 							return TagFailure;
490 						}
491 					}
492 					file->fields.list [entry->fields.count].key = key;
493 					file->fields.list [entry->fields.count].value = value;
494 					++entry->fields.count;
495 				}
496 			}
497 		}
498 	}
499 	return TagSuccess;
500 }
501 
isOdd(unsigned int i)502 static int isOdd (unsigned int i)
503 {
504 	return  (i % 2);
505 }
506 
countContinuousBackslashesBackward(const char * from,const char * till)507 static unsigned int countContinuousBackslashesBackward(const char *from,
508 						     const char *till)
509 {
510 	unsigned int counter = 0;
511 
512 	for (; from > till; from--)
513 	{
514 		if (*from == '\\')
515 			counter++;
516 		else
517 			break;
518 	}
519 	return counter;
520 }
521 
parseTagLine(tagFile * file,tagEntry * const entry,int * err)522 static tagResult parseTagLine (tagFile *file, tagEntry *const entry, int *err)
523 {
524 	int i;
525 	char *p = file->line.buffer;
526 	size_t p_len = strlen (p);
527 	char *tab = strchr (p, TAB);
528 
529 	memset(entry, 0, sizeof(*entry));
530 
531 	entry->name = p;
532 	if (tab != NULL)
533 	{
534 		*tab = '\0';
535 	}
536 
537 	/* When unescaping, the input string becomes shorter.
538 	 * e.g. \t occupies two bytes on the tag file.
539 	 * It is converted to 0x9 and occupies one byte.
540 	 * memmove called here for shortening the line
541 	 * buffer. */
542 	while (*p != '\0')
543 	{
544 		const char *next = p;
545 		int ch = readTagCharacter (&next);
546 		size_t skip = next - p;
547 
548 		*p = (char) ch;
549 		p++;
550 		p_len -= skip;
551 		if (skip > 1)
552 		{
553 			/* + 1 is for moving the area including the last '\0'. */
554 			memmove (p, next, p_len + 1);
555 			if (tab)
556 				tab -= skip - 1;
557 		}
558 	}
559 
560 	if (tab != NULL)
561 	{
562 		p = tab + 1;
563 		entry->file = p;
564 		tab = strchr (p, TAB);
565 		if (tab != NULL)
566 		{
567 			int fieldsPresent;
568 			int combinedPattern;
569 			*tab = '\0';
570 			p = tab + 1;
571 			if (*p == '/'  ||  *p == '?')
572 			{
573 				/* parse pattern */
574 				int delimiter = *(unsigned char*) p;
575 				entry->address.lineNumber = 0;
576 				entry->address.pattern = p;
577 				do
578 				{
579 					p = strchr (p + 1, delimiter);
580 				} while (p != NULL
581 					 &&  isOdd (countContinuousBackslashesBackward (p - 1,
582 											entry->address.pattern)));
583 
584 				if (p == NULL)
585 				{
586 					/* TODO: invalid pattern */
587 				}
588 				else
589 					++p;
590 			}
591 			else if (isdigit ((int) *(unsigned char*) p))
592 			{
593 				/* parse line number */
594 				entry->address.pattern = p;
595 				entry->address.lineNumber = atol (p);
596 				while (isdigit ((int) *(unsigned char*) p))
597 					++p;
598 				if (p)
599 				{
600 					combinedPattern = (strncmp (p, ";/", 2) == 0) ||
601 											(strncmp (p, ";?", 2) == 0);
602 					if (combinedPattern)
603 					{
604 						++p;
605 						/* parse pattern */
606 						int delimiter = *(unsigned char*) p;
607 						do
608 						{
609 							p = strchr (p + 1, delimiter);
610 						} while (p != NULL
611 							 &&  isOdd (countContinuousBackslashesBackward (p - 1,
612 													entry->address.pattern)));
613 
614 						if (p == NULL)
615 						{
616 							/* TODO: invalid pattern */
617 						}
618 						else
619 							++p;
620 					}
621 				}
622 			}
623 			else
624 			{
625 				/* TODO: invalid pattern */
626 			}
627 
628 			if (p)
629 			{
630 				fieldsPresent = (strncmp (p, ";\"", 2) == 0);
631 				*p = '\0';
632 				if (fieldsPresent)
633 				{
634 					if (parseExtensionFields (file, entry, p + 2, err) != TagSuccess)
635 						return TagFailure;
636 				}
637 			}
638 		}
639 	}
640 	if (entry->fields.count > 0)
641 		entry->fields.list = file->fields.list;
642 	for (i = entry->fields.count  ;  i < file->fields.max  ;  ++i)
643 	{
644 		file->fields.list [i].key = NULL;
645 		file->fields.list [i].value = NULL;
646 	}
647 	return TagSuccess;
648 }
649 
duplicate(const char * str)650 static char *duplicate (const char *str)
651 {
652 	char *result = NULL;
653 	if (str != NULL)
654 	{
655 		result = strdup (str);
656 		if (result == NULL)
657 			perror (NULL);
658 	}
659 	return result;
660 }
661 
isPseudoTagLine(const char * buffer)662 static int isPseudoTagLine (const char *buffer)
663 {
664 	return (strncmp (buffer, PseudoTagPrefix, PseudoTagPrefixLength) == 0);
665 }
666 
readPseudoTags(tagFile * const file,tagFileInfo * const info)667 static tagResult readPseudoTags (tagFile *const file, tagFileInfo *const info)
668 {
669 	fpos_t startOfLine;
670 	int err = 0;
671 	tagResult result = TagSuccess;
672 	const size_t prefixLength = strlen (PseudoTagPrefix);
673 
674 	info->file.format     = 1;
675 	info->file.sort       = TAG_UNSORTED;
676 	info->program.author  = NULL;
677 	info->program.name    = NULL;
678 	info->program.url     = NULL;
679 	info->program.version = NULL;
680 
681 	while (1)
682 	{
683 		if (fgetpos (file->fp, &startOfLine) < 0)
684 		{
685 			err = errno;
686 			break;
687 		}
688 		if (! readTagLine (file, &err))
689 			break;
690 		if (!isPseudoTagLine (file->line.buffer))
691 			break;
692 		else
693 		{
694 			tagEntry entry;
695 			const char *key, *value;
696 			if (parseTagLine (file, &entry, &err) != TagSuccess)
697 				break;
698 			key = entry.name + prefixLength;
699 			value = entry.file;
700 			if (strcmp (key, "TAG_FILE_SORTED") == 0)
701 			{
702 				char *endptr = NULL;
703 				long m = strtol (value, &endptr, 10);
704 				if (*endptr != '\0' || m < 0 || m > 2)
705 				{
706 					err = TagErrnoUnexpectedSortedMethod;
707 					break;
708 				}
709 				file->sortMethod = (tagSortType) m;
710 			}
711 			else if (strcmp (key, "TAG_FILE_FORMAT") == 0)
712 			{
713 				char *endptr = NULL;
714 				long m = strtol (value, &endptr, 10);
715 				if (*endptr != '\0' || m < 1 || m > 2)
716 				{
717 					err = TagErrnoUnexpectedFormat;
718 					break;
719 				}
720 				file->format = (short) m;
721 			}
722 			else if (strcmp (key, "TAG_PROGRAM_AUTHOR") == 0)
723 			{
724 				file->program.author = duplicate (value);
725 				if (value && file->program.author == NULL)
726 				{
727 					err = ENOMEM;
728 					break;
729 				}
730 			}
731 			else if (strcmp (key, "TAG_PROGRAM_NAME") == 0)
732 			{
733 				file->program.name = duplicate (value);
734 				if (value && file->program.name == NULL)
735 				{
736 					err = ENOMEM;
737 					break;
738 				}
739 			}
740 			else if (strcmp (key, "TAG_PROGRAM_URL") == 0)
741 			{
742 				file->program.url = duplicate (value);
743 				if (value && file->program.url == NULL)
744 				{
745 					err = ENOMEM;
746 					break;
747 				}
748 			}
749 			else if (strcmp (key, "TAG_PROGRAM_VERSION") == 0)
750 			{
751 				file->program.version = duplicate (value);
752 				if (value && file->program.version == NULL)
753 				{
754 					err = ENOMEM;
755 					break;
756 				}
757 			}
758 
759 			info->file.format     = file->format;
760 			info->file.sort       = file->sortMethod;
761 			info->program.author  = file->program.author;
762 			info->program.name    = file->program.name;
763 			info->program.url     = file->program.url;
764 			info->program.version = file->program.version;
765 		}
766 	}
767 	if (fsetpos (file->fp, &startOfLine) < 0)
768 		err = errno;
769 
770 	info->status.error_number = err;
771 	if (err)
772 		result = TagFailure;
773 	return result;
774 }
775 
doesFilePointPseudoTag(tagFile * const file,void * unused)776 static int doesFilePointPseudoTag (tagFile *const file, void *unused)
777 {
778 	return isPseudoTagLine (file->name.buffer);
779 }
780 
gotoFirstLogicalTag(tagFile * const file)781 static tagResult gotoFirstLogicalTag (tagFile *const file)
782 {
783 	fpos_t startOfLine;
784 
785 	if (readtags_fseek(file->fp, 0, SEEK_SET) == -1)
786 	{
787 		file->err = errno;
788 		return TagFailure;
789 	}
790 
791 	while (1)
792 	{
793 		if (fgetpos (file->fp, &startOfLine) < 0)
794 		{
795 			file->err = errno;
796 			return TagFailure;
797 		}
798 		if (! readTagLine (file, &file->err))
799 		{
800 			if (file->err)
801 				return TagFailure;
802 			break;
803 		}
804 		if (!isPseudoTagLine (file->line.buffer))
805 			break;
806 	}
807 	if (fsetpos (file->fp, &startOfLine) < 0)
808 	{
809 		file->err = errno;
810 		return TagFailure;
811 	}
812 	return TagSuccess;
813 }
814 
initialize(const char * const filePath,tagFileInfo * const info)815 static tagFile *initialize (const char *const filePath, tagFileInfo *const info)
816 {
817 	tagFile *result = (tagFile*) calloc ((size_t) 1, sizeof (tagFile));
818 
819 	if (result == NULL)
820 	{
821 		info->status.opened = 0;
822 		info->status.error_number = ENOMEM;
823 		return NULL;
824 	}
825 
826 	if (growString (&result->line) != TagSuccess)
827 		goto mem_error;
828 	if (growString (&result->name) != TagSuccess)
829 		goto mem_error;
830 	result->fields.max = 20;
831 	result->fields.list = (tagExtensionField*) calloc (
832 		result->fields.max, sizeof (tagExtensionField));
833 	if (result->fields.list == NULL)
834 		goto mem_error;
835 	result->fp = fopen (filePath, "rb");
836 	if (result->fp == NULL)
837 	{
838 		info->status.error_number = errno;
839 		goto file_error;
840 	}
841 
842 	/* Record the size of the tags file to `size` field of result. */
843 	if (readtags_fseek (result->fp, 0, SEEK_END) == -1)
844 	{
845 		info->status.error_number = errno;
846 		goto file_error;
847 	}
848 	result->size = readtags_ftell (result->fp);
849 	if (result->size == -1)
850 	{
851 		/* fseek() retruns an int value.
852 		 * We observed following behavior on Windows;
853 		 * if sizeof(int) of the platform is too small for
854 		 * representing the size of the tags file, fseek()
855 		 * returns -1 and it doesn't set errno.
856 		 */
857 		info->status.error_number = errno;
858 		if (info->status.error_number == 0)
859 			info->status.error_number = TagErrnoFileMaybeTooBig;
860 
861 		goto file_error;
862 	}
863 	if (readtags_fseek(result->fp, 0, SEEK_SET) == -1)
864 	{
865 		info->status.error_number = errno;
866 		goto file_error;
867 	}
868 
869 	if (readPseudoTags (result, info) == TagFailure)
870 		goto file_error;
871 
872 	info->status.opened = 1;
873 	result->initialized = 1;
874 
875 	return result;
876  mem_error:
877 	info->status.error_number = ENOMEM;
878  file_error:
879 	free (result->line.buffer);
880 	free (result->name.buffer);
881 	free (result->fields.list);
882 	if (result->fp)
883 		fclose (result->fp);
884 	free (result);
885 	info->status.opened = 0;
886 	return NULL;
887 }
888 
terminate(tagFile * const file)889 static void terminate (tagFile *const file)
890 {
891 	fclose (file->fp);
892 
893 	free (file->line.buffer);
894 	free (file->name.buffer);
895 	free (file->fields.list);
896 
897 	if (file->program.author != NULL)
898 		free (file->program.author);
899 	if (file->program.name != NULL)
900 		free (file->program.name);
901 	if (file->program.url != NULL)
902 		free (file->program.url);
903 	if (file->program.version != NULL)
904 		free (file->program.version);
905 	if (file->search.name != NULL)
906 		free (file->search.name);
907 
908 	memset (file, 0, sizeof (tagFile));
909 
910 	free (file);
911 }
912 
readNext(tagFile * const file,tagEntry * const entry)913 static tagResult readNext (tagFile *const file, tagEntry *const entry)
914 {
915 	tagResult result;
916 
917 	if (file == NULL)
918 		return TagFailure;
919 
920 	if (! file->initialized)
921 	{
922 		file->err = TagErrnoInvalidArgument;
923 		return TagFailure;
924 	}
925 
926 	if (! readTagLine (file, &file->err))
927 		return TagFailure;
928 
929 	result = (entry != NULL)
930 		? parseTagLine (file, entry, &file->err)
931 		: TagSuccess;
932 
933 	return result;
934 }
935 
readFieldValue(const tagEntry * const entry,const char * const key)936 static const char *readFieldValue (
937 	const tagEntry *const entry, const char *const key)
938 {
939 	const char *result = NULL;
940 	int i;
941 	if (strcmp (key, "kind") == 0)
942 		result = entry->kind;
943 	else if (strcmp (key, "file") == 0)
944 		result = EmptyString;
945 	else for (i = 0  ;  i < entry->fields.count  &&  result == NULL  ;  ++i)
946 		if (strcmp (entry->fields.list [i].key, key) == 0)
947 			result = entry->fields.list [i].value;
948 	return result;
949 }
950 
readTagLineSeek(tagFile * const file,const rt_off_t pos)951 static int readTagLineSeek (tagFile *const file, const rt_off_t pos)
952 {
953 	if (readtags_fseek (file->fp, pos, SEEK_SET) < 0)
954 	{
955 		file->err = errno;
956 		return 0;
957 	}
958 
959 	/* read probable partial line */
960 	if (!readTagLine (file, &file->err))
961 		return 0;
962 
963 	/* read complete line */
964 	if (pos > 0)
965 		return readTagLine (file, &file->err);
966 
967 	return 1;
968 }
969 
nameComparison(tagFile * const file)970 static int nameComparison (tagFile *const file)
971 {
972 	int result;
973 	if (file->search.ignorecase)
974 	{
975 		if (file->search.partial)
976 			result = tagnuppercmp (file->search.name, file->name.buffer,
977 					file->search.nameLength);
978 		else
979 			result = taguppercmp (file->search.name, file->name.buffer);
980 	}
981 	else
982 	{
983 		if (file->search.partial)
984 			result = tagncmp (file->search.name, file->name.buffer,
985 					file->search.nameLength);
986 		else
987 			result = tagcmp (file->search.name, file->name.buffer);
988 	}
989 	return result;
990 }
991 
findFirstNonMatchBefore(tagFile * const file)992 static tagResult findFirstNonMatchBefore (tagFile *const file)
993 {
994 #define JUMP_BACK 512
995 	int more_lines;
996 	int comp;
997 	rt_off_t start = file->pos;
998 	rt_off_t pos = start;
999 	do
1000 	{
1001 		if (pos < (rt_off_t) JUMP_BACK)
1002 			pos = 0;
1003 		else
1004 			pos = pos - JUMP_BACK;
1005 		more_lines = readTagLineSeek (file, pos);
1006 		if (more_lines == 0 && file->err)
1007 			return TagFailure;
1008 		comp = nameComparison (file);
1009 	} while (more_lines  &&  comp == 0  &&  pos > 0  &&  pos < start);
1010 	return TagSuccess;
1011 }
1012 
findFirstMatchBefore(tagFile * const file)1013 static tagResult findFirstMatchBefore (tagFile *const file)
1014 {
1015 	tagResult result = TagFailure;
1016 	int more_lines;
1017 	rt_off_t start = file->pos;
1018 	if (findFirstNonMatchBefore (file) != TagSuccess)
1019 		return TagFailure;
1020 	do
1021 	{
1022 		more_lines = readTagLine (file, &file->err);
1023 		if (more_lines == 0 && file->err)
1024 			return TagFailure;
1025 		if (nameComparison (file) == 0)
1026 			result = TagSuccess;
1027 	} while (more_lines  &&  result != TagSuccess  &&  file->pos < start);
1028 	return result;
1029 }
1030 
findBinary(tagFile * const file)1031 static tagResult findBinary (tagFile *const file)
1032 {
1033 	tagResult result = TagFailure;
1034 	rt_off_t lower_limit = 0;
1035 	rt_off_t upper_limit = file->size;
1036 	rt_off_t last_pos = 0;
1037 	rt_off_t pos = upper_limit / 2;
1038 	while (result != TagSuccess)
1039 	{
1040 		if (! readTagLineSeek (file, pos))
1041 		{
1042 			if (file->err)
1043 				break;
1044 			/* in case we fell off end of file */
1045 			result = findFirstMatchBefore (file);
1046 			break;
1047 		}
1048 		else if (pos == last_pos)
1049 		{
1050 			/* prevent infinite loop if we backed up to beginning of file */
1051 			break;
1052 		}
1053 		else
1054 		{
1055 			const int comp = nameComparison (file);
1056 			last_pos = pos;
1057 			if (comp < 0)
1058 			{
1059 				upper_limit = pos;
1060 				pos = lower_limit + ((upper_limit - lower_limit) / 2);
1061 			}
1062 			else if (comp > 0)
1063 			{
1064 				lower_limit = pos;
1065 				pos = lower_limit + ((upper_limit - lower_limit) / 2);
1066 			}
1067 			else if (pos == 0)
1068 				result = TagSuccess;
1069 			else
1070 			{
1071 				result = findFirstMatchBefore (file);
1072 				if (result != TagSuccess && file->err)
1073 					break;
1074 			}
1075 		}
1076 	}
1077 	return result;
1078 }
1079 
findSequentialFull(tagFile * const file,int (* isAcceptable)(tagFile * const,void *),void * data)1080 static tagResult findSequentialFull (tagFile *const file,
1081 									 int (* isAcceptable) (tagFile *const, void *),
1082 									 void *data)
1083 {
1084 	if (file == NULL)
1085 		return TagFailure;
1086 
1087 	if (!file->initialized || file->err)
1088 	{
1089 		file->err = TagErrnoInvalidArgument;
1090 		return TagFailure;
1091 	}
1092 
1093 	tagResult result = TagFailure;
1094 	while (result == TagFailure)
1095 	{
1096 		if (! readTagLine (file, &file->err))
1097 			break;
1098 		if (isAcceptable (file, data))
1099 			result = TagSuccess;
1100 	}
1101 	return result;
1102 }
1103 
nameAcceptable(tagFile * const file,void * unused)1104 static int nameAcceptable (tagFile *const file, void *unused)
1105 {
1106 	return (nameComparison (file) == 0);
1107 }
1108 
findSequential(tagFile * const file)1109 static tagResult findSequential (tagFile *const file)
1110 {
1111 	return findSequentialFull (file, nameAcceptable, NULL);
1112 }
1113 
find(tagFile * const file,tagEntry * const entry,const char * const name,const int options)1114 static tagResult find (tagFile *const file, tagEntry *const entry,
1115 					   const char *const name, const int options)
1116 {
1117 	tagResult result;
1118 	if (file->search.name != NULL)
1119 		free (file->search.name);
1120 	file->search.name = duplicate (name);
1121 	if (file->search.name == NULL)
1122 	{
1123 		file->err = ENOMEM;
1124 		return TagFailure;
1125 	}
1126 	file->search.nameLength = strlen (name);
1127 	file->search.partial = (options & TAG_PARTIALMATCH) != 0;
1128 	file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
1129 	if (readtags_fseek (file->fp, 0, SEEK_END) < 0)
1130 	{
1131 		file->err = errno;
1132 		return TagFailure;
1133 	}
1134 	file->size = readtags_ftell (file->fp);
1135 	if (file->size == -1)
1136 	{
1137 		file->err = errno;
1138 		return TagFailure;
1139 	}
1140 	if (readtags_fseek(file->fp, 0, SEEK_SET) == -1)
1141 	{
1142 		file->err = errno;
1143 		return TagFailure;
1144 	}
1145 	if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
1146 		(file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase))
1147 	{
1148 		result = findBinary (file);
1149 		if (result == TagFailure && file->err)
1150 			return TagFailure;
1151 	}
1152 	else
1153 	{
1154 		result = findSequential (file);
1155 		if (result == TagFailure && file->err)
1156 			return TagFailure;
1157 	}
1158 
1159 	if (result != TagSuccess)
1160 		file->search.pos = file->size;
1161 	else
1162 	{
1163 		file->search.pos = file->pos;
1164 		result = (entry != NULL)
1165 			? parseTagLine (file, entry, &file->err)
1166 			: TagSuccess;
1167 	}
1168 	return result;
1169 }
1170 
findNextFull(tagFile * const file,tagEntry * const entry,int sorted,int (* isAcceptable)(tagFile * const,void *),void * data)1171 static tagResult findNextFull (tagFile *const file, tagEntry *const entry,
1172 							   int sorted,
1173 							   int (* isAcceptable) (tagFile *const, void *),
1174 							   void *data)
1175 {
1176 	tagResult result;
1177 	if (sorted)
1178 	{
1179 		result = tagsNext (file, entry);
1180 		if (result == TagSuccess  && !isAcceptable (file, data))
1181 			result = TagFailure;
1182 	}
1183 	else
1184 	{
1185 		result = findSequentialFull (file, isAcceptable, data);
1186 		if (result == TagSuccess  &&  entry != NULL)
1187 			result = parseTagLine (file, entry, &file->err);
1188 	}
1189 	return result;
1190 }
1191 
findNext(tagFile * const file,tagEntry * const entry)1192 static tagResult findNext (tagFile *const file, tagEntry *const entry)
1193 {
1194 	return findNextFull (file, entry,
1195 						 (file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
1196 						 (file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase),
1197 						 nameAcceptable, NULL);
1198 }
1199 
findPseudoTag(tagFile * const file,int rewindBeforeFinding,tagEntry * const entry)1200 static tagResult findPseudoTag (tagFile *const file, int rewindBeforeFinding, tagEntry *const entry)
1201 {
1202 	if (file == NULL)
1203 		return TagFailure;
1204 
1205 	if (!file->initialized || file->err)
1206 	{
1207 		file->err = TagErrnoInvalidArgument;
1208 		return TagFailure;
1209 	}
1210 
1211 	if (rewindBeforeFinding)
1212 	{
1213 		if (readtags_fseek(file->fp, 0, SEEK_SET) == -1)
1214 		{
1215 			file->err = errno;
1216 			return TagFailure;
1217 		}
1218 	}
1219 	return findNextFull (file, entry,
1220 						 (file->sortMethod == TAG_SORTED || file->sortMethod == TAG_FOLDSORTED),
1221 						 doesFilePointPseudoTag,
1222 						 NULL);
1223 }
1224 
1225 
1226 /*
1227 *  EXTERNAL INTERFACE
1228 */
1229 
tagsOpen(const char * const filePath,tagFileInfo * const info)1230 extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info)
1231 {
1232 	tagFileInfo infoDummy;
1233 	return initialize (filePath, info? info: &infoDummy);
1234 }
1235 
tagsSetSortType(tagFile * const file,const tagSortType type)1236 extern tagResult tagsSetSortType (tagFile *const file, const tagSortType type)
1237 {
1238 	if (file == NULL)
1239 		return TagFailure;
1240 
1241 	if (!file->initialized || file->err)
1242 	{
1243 		file->err = TagErrnoInvalidArgument;
1244 		return TagFailure;
1245 	}
1246 
1247 	switch (type)
1248 	{
1249 	case TAG_UNSORTED:
1250 	case TAG_SORTED:
1251 	case TAG_FOLDSORTED:
1252 		file->sortMethod = type;
1253 		return TagSuccess;
1254 	default:
1255 		file->err = TagErrnoUnexpectedSortedMethod;
1256 		return TagFailure;
1257 	}
1258 }
1259 
tagsFirst(tagFile * const file,tagEntry * const entry)1260 extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry)
1261 {
1262 	if (file == NULL)
1263 		return TagFailure;
1264 
1265 	if (!file->initialized || file->err)
1266 	{
1267 		file->err = TagErrnoInvalidArgument;
1268 		return TagFailure;
1269 	}
1270 
1271 	if (gotoFirstLogicalTag (file) != TagSuccess)
1272 		return TagFailure;
1273 	return readNext (file, entry);
1274 }
1275 
tagsNext(tagFile * const file,tagEntry * const entry)1276 extern tagResult tagsNext (tagFile *const file, tagEntry *const entry)
1277 {
1278 	if (file == NULL)
1279 		return TagFailure;
1280 
1281 	if (!file->initialized || file->err)
1282 	{
1283 		file->err = TagErrnoInvalidArgument;
1284 		return TagFailure;
1285 	}
1286 
1287 	return readNext (file, entry);
1288 }
1289 
tagsField(const tagEntry * const entry,const char * const key)1290 extern const char *tagsField (const tagEntry *const entry, const char *const key)
1291 {
1292 	const char *result = NULL;
1293 	if (entry != NULL)
1294 		result = readFieldValue (entry, key);
1295 	return result;
1296 }
1297 
tagsFind(tagFile * const file,tagEntry * const entry,const char * const name,const int options)1298 extern tagResult tagsFind (tagFile *const file, tagEntry *const entry,
1299 						   const char *const name, const int options)
1300 {
1301 	if (file == NULL)
1302 		return TagFailure;
1303 
1304 	if (!file->initialized || file->err)
1305 	{
1306 		file->err = TagErrnoInvalidArgument;
1307 		return TagFailure;
1308 	}
1309 
1310 	return find (file, entry, name, options);
1311 }
1312 
tagsFindNext(tagFile * const file,tagEntry * const entry)1313 extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry)
1314 {
1315 	if (file == NULL)
1316 		return TagFailure;
1317 
1318 	if (!file->initialized || file->err)
1319 	{
1320 		file->err = TagErrnoInvalidArgument;
1321 		return TagFailure;
1322 	}
1323 
1324 	return findNext (file, entry);
1325 }
1326 
tagsFirstPseudoTag(tagFile * const file,tagEntry * const entry)1327 extern tagResult tagsFirstPseudoTag (tagFile *const file, tagEntry *const entry)
1328 {
1329 	return findPseudoTag (file, 1, entry);
1330 }
1331 
tagsNextPseudoTag(tagFile * const file,tagEntry * const entry)1332 extern tagResult tagsNextPseudoTag (tagFile *const file, tagEntry *const entry)
1333 {
1334 	return findPseudoTag (file, 0, entry);
1335 }
1336 
tagsClose(tagFile * const file)1337 extern tagResult tagsClose (tagFile *const file)
1338 {
1339 	tagResult result = TagFailure;
1340 	if (file != NULL  &&  file->initialized)
1341 	{
1342 		terminate (file);
1343 		result = TagSuccess;
1344 	}
1345 	return result;
1346 }
1347 
tagsGetErrno(tagFile * const file)1348 extern int tagsGetErrno (tagFile *const file)
1349 {
1350 	if (file == NULL)
1351 		return TagErrnoInvalidArgument;
1352 	return file->err;
1353 }
1354