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