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