1 /*
2 *   $Id: readtags.c,v 1.1 2009/11/10 10:23:01 yetanothergeek Exp $
3 *
4 *   Copyright (c) 1996-2003, Darren Hiebert
5 *
6 *   This source code is released into the public domain.
7 *
8 *   This module contains functions for reading tag files.
9 */
10 
11 /*
12 *   INCLUDE FILES
13 */
14 #include <stdlib.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <stdio.h>
18 #include <errno.h>
19 
20 #include <sys/types.h>  /* to declare off_t */
21 
22 #include "intl.h"
23 #include "readtags.h"
24 
25 /*
26 *   MACROS
27 */
28 #define TAB '\t'
29 
30 extern void tag_warning(const char*msg);
31 /*
32 *   DATA DECLARATIONS
33 */
34 typedef struct {
35   size_t size;
36   char *buffer;
37 } vstring;
38 
39 /* Information about current tag file */
40 struct sTagFile {
41     /* has the file been opened and this structure initialized? */
42   short initialized;
43     /* format of tag file */
44   short format;
45     /* how is the tag file sorted? */
46   sortType sortMethod;
47     /* pointer to file structure */
48   FILE* fp;
49     /* file position of first character of `line' */
50   off_t pos;
51     /* size of tag file in seekable positions */
52   off_t size;
53     /* last line read */
54   vstring line;
55     /* name of tag in last line read */
56   vstring name;
57     /* defines tag search state */
58   struct {
59         /* file position of last match for tag */
60       off_t pos;
61         /* name of tag last searched for */
62       char *name;
63         /* length of name for partial matches */
64       size_t nameLength;
65         /* peforming partial match */
66       short partial;
67         /* ignoring case */
68       short ignorecase;
69   } search;
70     /* miscellaneous extension fields */
71   struct {
72         /* number of entries in `list' */
73       unsigned short max;
74         /* list of key value pairs */
75       tagExtensionField *list;
76   } fields;
77     /* buffers to be freed at close */
78   struct {
79       /* name of program author */
80     char *author;
81       /* name of program */
82     char *name;
83       /* URL of distribution */
84     char *url;
85       /* program version */
86     char *version;
87   } program;
88 };
89 
90 /*
91 *   DATA DEFINITIONS
92 */
93 const char *const EmptyString = "";
94 const char *const PseudoTagPrefix = "!_";
95 
96 /*
97 *   FUNCTION DEFINITIONS
98 */
99 
100 /*
101  * Compare two strings, ignoring case.
102  * Return 0 for match, < 0 for smaller, > 0 for bigger
103  * Make sure case is folded to uppercase in comparison (like for 'sort -f')
104  * This makes a difference when one of the chars lies between upper and lower
105  * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
106  */
struppercmp(const char * s1,const char * s2)107 static int struppercmp (const char *s1, const char *s2)
108 {
109   int result;
110   do
111   {
112     result = toupper ((int) *s1) - toupper ((int) *s2);
113   } while (result == 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
114   return result;
115 }
116 
strnuppercmp(const char * s1,const char * s2,size_t n)117 static int strnuppercmp (const char *s1, const char *s2, size_t n)
118 {
119   int result;
120   do
121   {
122     result = toupper ((int) *s1) - toupper ((int) *s2);
123   } while (result == 0  &&  --n > 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
124   return result;
125 }
126 
growString(vstring * s)127 static int growString (vstring *s)
128 {
129   int result = 0;
130   size_t newLength;
131   char *newLine;
132   if (s->size == 0)
133   {
134     newLength = 128;
135     newLine = (char*) malloc (newLength);
136     *newLine = '\0';
137   }
138   else
139   {
140     newLength = 2 * s->size;
141     newLine = (char*) realloc (s->buffer, newLength);
142   }
143   if (newLine == NULL)
144     tag_warning(_("string too large"));
145   else
146   {
147     s->buffer = newLine;
148     s->size = newLength;
149     result = 1;
150   }
151   return result;
152 }
153 
154 /* Copy name of tag out of tag line */
copyName(tagFile * const file)155 static void copyName (tagFile *const file)
156 {
157   size_t length;
158   const char *end = strchr (file->line.buffer, '\t');
159   if (end == NULL)
160   {
161     end = strchr (file->line.buffer, '\n');
162     if (end == NULL)
163       end = strchr (file->line.buffer, '\r');
164   }
165   if (end != NULL)
166     length = end - file->line.buffer;
167   else
168     length = strlen (file->line.buffer);
169   while (length >= file->name.size)
170     growString (&file->name);
171   strncpy (file->name.buffer, file->line.buffer, length);
172   file->name.buffer [length] = '\0';
173 }
174 
readTagLineRaw(tagFile * const file)175 static int readTagLineRaw (tagFile *const file)
176 {
177   int result = 1;
178   int reReadLine;
179 
180   /*  If reading the line places any character other than a null or a
181    *  newline at the last character position in the buffer (one less than
182    *  the buffer size), then we must resize the buffer and reattempt to read
183    *  the line.
184    */
185   do
186   {
187     char *const pLastChar = file->line.buffer + file->line.size - 2;
188     char *line;
189 
190     file->pos = ftell (file->fp);
191     reReadLine = 0;
192     *pLastChar = '\0';
193     line = fgets (file->line.buffer, (int) file->line.size, file->fp);
194     if (line == NULL)
195     {
196       /* read error */
197       if (! feof (file->fp))
198         tag_warning("readTagLine");
199       result = 0;
200     }
201     else if (*pLastChar != '\0'  &&
202           *pLastChar != '\n'  &&  *pLastChar != '\r')
203     {
204       /*  buffer overflow */
205       growString (&file->line);
206       fseek (file->fp, file->pos, SEEK_SET);
207       reReadLine = 1;
208     }
209     else
210     {
211       size_t i = strlen (file->line.buffer);
212       while (i > 0  &&
213            (file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r'))
214       {
215         file->line.buffer [i - 1] = '\0';
216         --i;
217       }
218     }
219   } while (reReadLine  &&  result);
220   if (result)
221     copyName (file);
222   return result;
223 }
224 
readTagLine(tagFile * const file)225 static int readTagLine (tagFile *const file)
226 {
227   int result;
228   do
229   {
230     result = readTagLineRaw (file);
231   } while (result && *file->name.buffer == '\0');
232   return result;
233 }
234 
growFields(tagFile * const file)235 static tagResult growFields (tagFile *const file)
236 {
237   tagResult result = TagFailure;
238   unsigned short newCount = (unsigned short) 2 * file->fields.max;
239   tagExtensionField *newFields = (tagExtensionField*)
240       realloc (file->fields.list, newCount * sizeof (tagExtensionField));
241   if (newFields == NULL)
242     tag_warning(_("too many extension fields"));
243   else
244   {
245     file->fields.list = newFields;
246     file->fields.max = newCount;
247     result = TagSuccess;
248   }
249   return result;
250 }
251 
parseExtensionFields(tagFile * const file,tagEntry * const entry,char * const string)252 static void parseExtensionFields (tagFile *const file, tagEntry *const entry,
253                   char *const string)
254 {
255   char *p = string;
256   while (p != NULL  &&  *p != '\0')
257   {
258     while (*p == TAB)
259       *p++ = '\0';
260     if (*p != '\0')
261     {
262       char *colon;
263       char *field = p;
264       p = strchr (p, TAB);
265       if (p != NULL)
266         *p++ = '\0';
267       colon = strchr (field, ':');
268       if (colon == NULL)
269         entry->kind = field;
270       else
271       {
272         const char *key = field;
273         const char *value = colon + 1;
274         *colon = '\0';
275         if (strcmp (key, "kind") == 0)
276           entry->kind = value;
277         else if (strcmp (key, "file") == 0)
278           entry->fileScope = 1;
279         else if (strcmp (key, "line") == 0)
280           entry->address.lineNumber = atol (value);
281         else
282         {
283           if (entry->fields.count == file->fields.max)
284             growFields (file);
285           file->fields.list [entry->fields.count].key = key;
286           file->fields.list [entry->fields.count].value = value;
287           ++entry->fields.count;
288         }
289       }
290     }
291   }
292 }
293 
parseTagLine(tagFile * file,tagEntry * const entry)294 static void parseTagLine (tagFile *file, tagEntry *const entry)
295 {
296   int i;
297   char *p = file->line.buffer;
298   char *tab = strchr (p, TAB);
299 
300   entry->fields.list = NULL;
301   entry->fields.count = 0;
302   entry->kind = NULL;
303   entry->fileScope = 0;
304 
305   entry->name = p;
306   if (tab != NULL)
307   {
308     *tab = '\0';
309     p = tab + 1;
310     entry->file = p;
311     tab = strchr (p, TAB);
312     if (tab != NULL)
313     {
314       int fieldsPresent;
315       *tab = '\0';
316       p = tab + 1;
317       if (*p == '/'  ||  *p == '?')
318       {
319         /* parse pattern */
320         int delimiter = *(unsigned char*) p;
321         entry->address.lineNumber = 0;
322         entry->address.pattern = p;
323         do
324         {
325           p = strchr (p + 1, delimiter);
326         } while (p != NULL  &&  *(p - 1) == '\\');
327         if (p == NULL)
328         {
329           /* invalid pattern */
330         }
331         else
332           ++p;
333       }
334       else if (isdigit ((int) *(unsigned char*) p))
335       {
336         /* parse line number */
337         entry->address.pattern = p;
338         entry->address.lineNumber = atol (p);
339         while (isdigit ((int) *(unsigned char*) p))
340           ++p;
341       }
342       else
343       {
344         /* invalid pattern */
345       }
346       fieldsPresent = (strncmp (p, ";\"", 2) == 0);
347       *p = '\0';
348       if (fieldsPresent)
349         parseExtensionFields (file, entry, p + 2);
350     }
351   }
352   if (entry->fields.count > 0)
353     entry->fields.list = file->fields.list;
354   for (i = entry->fields.count  ;  i < file->fields.max  ;  ++i)
355   {
356     file->fields.list [i].key = NULL;
357     file->fields.list [i].value = NULL;
358   }
359 }
360 
duplicate(const char * str)361 static char *duplicate (const char *str)
362 {
363   char *result = NULL;
364   if (str != NULL)
365   {
366     result = strdup (str);
367     if (result == NULL)
368       tag_warning("NULL");
369   }
370   return result;
371 }
372 
readPseudoTags(tagFile * const file,tagFileInfo * const info)373 static void readPseudoTags (tagFile *const file, tagFileInfo *const info)
374 {
375   fpos_t startOfLine;
376   const size_t prefixLength = strlen (PseudoTagPrefix);
377   if (info != NULL)
378   {
379     info->file.format     = 1;
380     info->file.sort       = TAG_UNSORTED;
381     info->program.author  = NULL;
382     info->program.name    = NULL;
383     info->program.url     = NULL;
384     info->program.version = NULL;
385   }
386   while (1)
387   {
388     fgetpos (file->fp, &startOfLine);
389     if (! readTagLine (file))
390       break;
391     if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
392       break;
393     else
394     {
395       tagEntry entry;
396       const char *key, *value;
397       parseTagLine (file, &entry);
398       key = entry.name + prefixLength;
399       value = entry.file;
400       if (strcmp (key, "TAG_FILE_SORTED") == 0)
401         file->sortMethod = (sortType) atoi (value);
402       else if (strcmp (key, "TAG_FILE_FORMAT") == 0)
403         file->format = (short) atoi (value);
404       else if (strcmp (key, "TAG_PROGRAM_AUTHOR") == 0)
405         file->program.author = duplicate (value);
406       else if (strcmp (key, "TAG_PROGRAM_NAME") == 0)
407         file->program.name = duplicate (value);
408       else if (strcmp (key, "TAG_PROGRAM_URL") == 0)
409         file->program.url = duplicate (value);
410       else if (strcmp (key, "TAG_PROGRAM_VERSION") == 0)
411         file->program.version = duplicate (value);
412       if (info != NULL)
413       {
414         info->file.format     = file->format;
415         info->file.sort       = file->sortMethod;
416         info->program.author  = file->program.author;
417         info->program.name    = file->program.name;
418         info->program.url     = file->program.url;
419         info->program.version = file->program.version;
420       }
421     }
422   }
423   fsetpos (file->fp, &startOfLine);
424 }
425 
gotoFirstLogicalTag(tagFile * const file)426 static void gotoFirstLogicalTag (tagFile *const file)
427 {
428   fpos_t startOfLine;
429   const size_t prefixLength = strlen (PseudoTagPrefix);
430   rewind (file->fp);
431   while (1)
432   {
433     fgetpos (file->fp, &startOfLine);
434     if (! readTagLine (file))
435       break;
436     if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
437       break;
438   }
439   fsetpos (file->fp, &startOfLine);
440 }
441 
initialize(const char * const filePath,tagFileInfo * const info)442 static tagFile *initialize (const char *const filePath, tagFileInfo *const info)
443 {
444   tagFile *result = (tagFile*) calloc ((size_t) 1, sizeof (tagFile));
445   if (result != NULL)
446   {
447     growString (&result->line);
448     growString (&result->name);
449     result->fields.max = 20;
450     result->fields.list = (tagExtensionField*) calloc (
451       result->fields.max, sizeof (tagExtensionField));
452     result->fp = fopen (filePath, "r");
453     if (result->fp == NULL)
454     {
455       free (result);
456       result = NULL;
457       info->status.error_number = errno;
458     }
459     else
460     {
461       fseek (result->fp, 0, SEEK_END);
462       result->size = ftell (result->fp);
463       rewind (result->fp);
464       readPseudoTags (result, info);
465       info->status.opened = 1;
466       result->initialized = 1;
467     }
468   }
469   return result;
470 }
471 
terminate(tagFile * const file)472 static void terminate (tagFile *const file)
473 {
474   fclose (file->fp);
475 
476   free (file->line.buffer);
477   free (file->name.buffer);
478   free (file->fields.list);
479 
480   if (file->program.author != NULL)
481     free (file->program.author);
482   if (file->program.name != NULL)
483     free (file->program.name);
484   if (file->program.url != NULL)
485     free (file->program.url);
486   if (file->program.version != NULL)
487     free (file->program.version);
488   if (file->search.name != NULL)
489     free (file->search.name);
490 
491   memset (file, 0, sizeof (tagFile));
492 
493   free (file);
494 }
495 
readNext(tagFile * const file,tagEntry * const entry)496 static tagResult readNext (tagFile *const file, tagEntry *const entry)
497 {
498   tagResult result;
499   if (file == NULL  ||  ! file->initialized)
500     result = TagFailure;
501   else if (! readTagLine (file))
502     result = TagFailure;
503   else
504   {
505     if (entry != NULL)
506       parseTagLine (file, entry);
507     result = TagSuccess;
508   }
509   return result;
510 }
511 
readFieldValue(const tagEntry * const entry,const char * const key)512 static const char *readFieldValue (
513   const tagEntry *const entry, const char *const key)
514 {
515   const char *result = NULL;
516   int i;
517   if (strcmp (key, "kind") == 0)
518     result = entry->kind;
519   else if (strcmp (key, "file") == 0)
520     result = EmptyString;
521   else for (i = 0  ;  i < entry->fields.count  &&  result == NULL  ;  ++i)
522     if (strcmp (entry->fields.list [i].key, key) == 0)
523       result = entry->fields.list [i].value;
524   return result;
525 }
526 
readTagLineSeek(tagFile * const file,const off_t pos)527 static int readTagLineSeek (tagFile *const file, const off_t pos)
528 {
529   int result = 0;
530   if (fseek (file->fp, pos, SEEK_SET) == 0)
531   {
532     result = readTagLine (file);  /* read probable partial line */
533     if (pos > 0  &&  result)
534       result = readTagLine (file);  /* read complete line */
535   }
536   return result;
537 }
538 
nameComparison(tagFile * const file)539 static int nameComparison (tagFile *const file)
540 {
541   int result;
542   if (file->search.ignorecase)
543   {
544     if (file->search.partial)
545       result = strnuppercmp (file->search.name, file->name.buffer,
546           file->search.nameLength);
547     else
548       result = struppercmp (file->search.name, file->name.buffer);
549   }
550   else
551   {
552     if (file->search.partial)
553       result = strncmp (file->search.name, file->name.buffer,
554           file->search.nameLength);
555     else
556       result = strcmp (file->search.name, file->name.buffer);
557   }
558   return result;
559 }
560 
findFirstNonMatchBefore(tagFile * const file)561 static void findFirstNonMatchBefore (tagFile *const file)
562 {
563 #define JUMP_BACK 512
564   int more_lines;
565   int comp;
566   off_t start = file->pos;
567   off_t pos = start;
568   do
569   {
570     if (pos < (off_t) JUMP_BACK)
571       pos = 0;
572     else
573       pos = pos - JUMP_BACK;
574     more_lines = readTagLineSeek (file, pos);
575     comp = nameComparison (file);
576   } while (more_lines  &&  comp == 0  &&  pos > 0  &&  pos < start);
577 }
578 
findFirstMatchBefore(tagFile * const file)579 static tagResult findFirstMatchBefore (tagFile *const file)
580 {
581   tagResult result = TagFailure;
582   int more_lines;
583   off_t start = file->pos;
584   findFirstNonMatchBefore (file);
585   do
586   {
587     more_lines = readTagLine (file);
588     if (nameComparison (file) == 0)
589       result = TagSuccess;
590   } while (more_lines  &&  result != TagSuccess  &&  file->pos < start);
591   return result;
592 }
593 
findBinary(tagFile * const file)594 static tagResult findBinary (tagFile *const file)
595 {
596   tagResult result = TagFailure;
597   off_t lower_limit = 0;
598   off_t upper_limit = file->size;
599   off_t last_pos = 0;
600   off_t pos = upper_limit / 2;
601   while (result != TagSuccess)
602   {
603     if (! readTagLineSeek (file, pos))
604     {
605       /* in case we fell off end of file */
606       result = findFirstMatchBefore (file);
607       break;
608     }
609     else if (pos == last_pos)
610     {
611       /* prevent infinite loop if we backed up to beginning of file */
612       break;
613     }
614     else
615     {
616       const int comp = nameComparison (file);
617       last_pos = pos;
618       if (comp < 0)
619       {
620         upper_limit = pos;
621         pos = lower_limit + ((upper_limit - lower_limit) / 2);
622       }
623       else if (comp > 0)
624       {
625         lower_limit = pos;
626         pos = lower_limit + ((upper_limit - lower_limit) / 2);
627       }
628       else if (pos == 0)
629         result = TagSuccess;
630       else
631         result = findFirstMatchBefore (file);
632     }
633   }
634   return result;
635 }
636 
findSequential(tagFile * const file)637 static tagResult findSequential (tagFile *const file)
638 {
639   tagResult result = TagFailure;
640   if (file->initialized)
641   {
642     while (result == TagFailure  &&  readTagLine (file))
643     {
644       if (nameComparison (file) == 0)
645         result = TagSuccess;
646     }
647   }
648   return result;
649 }
650 
find(tagFile * const file,tagEntry * const entry,const char * const name,const int options)651 static tagResult find (tagFile *const file, tagEntry *const entry,
652              const char *const name, const int options)
653 {
654   tagResult result;
655   if (file->search.name != NULL)
656     free (file->search.name);
657   file->search.name = duplicate (name);
658   file->search.nameLength = strlen (name);
659   file->search.partial = (options & TAG_PARTIALMATCH) != 0;
660   file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
661   fseek (file->fp, 0, SEEK_END);
662   file->size = ftell (file->fp);
663   rewind (file->fp);
664   if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
665     (file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase))
666   {
667     result = findBinary (file);
668   }
669   else
670   {
671     result = findSequential (file);
672   }
673 
674   if (result != TagSuccess)
675     file->search.pos = file->size;
676   else
677   {
678     file->search.pos = file->pos;
679     if (entry != NULL)
680       parseTagLine (file, entry);
681   }
682   return result;
683 }
684 
findNext(tagFile * const file,tagEntry * const entry)685 static tagResult findNext (tagFile *const file, tagEntry *const entry)
686 {
687   tagResult result;
688   if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
689     (file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase))
690   {
691     result = tagsNext (file, entry);
692     if (result == TagSuccess  && nameComparison (file) != 0)
693       result = TagFailure;
694   }
695   else
696   {
697     result = findSequential (file);
698     if (result == TagSuccess  &&  entry != NULL)
699       parseTagLine (file, entry);
700   }
701   return result;
702 }
703 
704 /*
705 *  EXTERNAL INTERFACE
706 */
707 
tagsOpen(const char * const filePath,tagFileInfo * const info)708 extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info)
709 {
710   return initialize (filePath, info);
711 }
712 
tagsSetSortType(tagFile * const file,const sortType type)713 extern tagResult tagsSetSortType (tagFile *const file, const sortType type)
714 {
715   tagResult result = TagFailure;
716   if (file != NULL  &&  file->initialized)
717   {
718     file->sortMethod = type;
719     result = TagSuccess;
720   }
721   return result;
722 }
723 
tagsFirst(tagFile * const file,tagEntry * const entry)724 extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry)
725 {
726   tagResult result = TagFailure;
727   if (file != NULL  &&  file->initialized)
728   {
729     gotoFirstLogicalTag (file);
730     result = readNext (file, entry);
731   }
732   return result;
733 }
734 
tagsNext(tagFile * const file,tagEntry * const entry)735 extern tagResult tagsNext (tagFile *const file, tagEntry *const entry)
736 {
737   tagResult result = TagFailure;
738   if (file != NULL  &&  file->initialized)
739     result = readNext (file, entry);
740   return result;
741 }
742 
tagsField(const tagEntry * const entry,const char * const key)743 extern const char *tagsField (const tagEntry *const entry, const char *const key)
744 {
745   const char *result = NULL;
746   if (entry != NULL)
747     result = readFieldValue (entry, key);
748   return result;
749 }
750 
tagsFind(tagFile * const file,tagEntry * const entry,const char * const name,const int options)751 extern tagResult tagsFind (tagFile *const file, tagEntry *const entry,
752                const char *const name, const int options)
753 {
754   tagResult result = TagFailure;
755   if (file != NULL  &&  file->initialized)
756     result = find (file, entry, name, options);
757   return result;
758 }
759 
tagsFindNext(tagFile * const file,tagEntry * const entry)760 extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry)
761 {
762   tagResult result = TagFailure;
763   if (file != NULL  &&  file->initialized)
764     result = findNext (file, entry);
765   return result;
766 }
767 
tagsClose(tagFile * const file)768 extern tagResult tagsClose (tagFile *const file)
769 {
770   tagResult result = TagFailure;
771   if (file != NULL  &&  file->initialized)
772   {
773     terminate (file);
774     result = TagSuccess;
775   }
776   return result;
777 }
778 
779