/ src / libnml / inifile / inifile.cc
inifile.cc
  1  /*****************************************************************************
  2   * Description: inifile.cc
  3   *   C++ INI file reader
  4   *
  5   *   Derived from a work by Fred Proctor & Will Shackleford
  6   *
  7   * Author:
  8   * License: GPL Version 2
  9   * System: Linux
 10   *    
 11   * Copyright (c) 2004 All rights reserved.
 12   *
 13   * Last change: 
 14   *****************************************************************************/
 15  
 16  #include <stdio.h>              /* FILE *, fopen(), fclose(), NULL */
 17  #include <stdlib.h>
 18  #include <string.h>             /* strstr() */
 19  #include <ctype.h>              /* isspace() */
 20  #include <fcntl.h>
 21  
 22  
 23  #include "config.h"
 24  #include "inifile.hh"
 25  
 26  #define MAX_EXTEND_LINES 20
 27  
 28  /// Return TRUE if the line has a line-ending problem
 29  static bool check_line_endings(const char *s) {
 30      if(!s) return false;
 31      for(; *s; s++ ) {
 32          if(*s == '\r') {
 33              char c = s[1];
 34              if(c == '\n' || c == '\0') {
 35                  static bool warned = 0;
 36                  if(!warned) 
 37                      fprintf(stderr, "inifile: warning: File contains DOS-style line endings.\n");
 38                  warned = true;
 39                  continue;
 40              }
 41              fprintf(stderr, "inifile: error: File contains ambiguous carriage returns\n");
 42              return true;
 43          }
 44      }
 45      return false;
 46  }
 47  
 48  IniFile::IniFile(int _errMask, FILE *_fp)
 49  {
 50      fp = _fp;
 51      errMask = _errMask;
 52      owned = false;
 53  
 54      if(fp != NULL)
 55          LockFile();
 56  }
 57  
 58  
 59  /*! Opens the file for reading. If a file was already open, it is closed
 60     and the new one opened.
 61  
 62     @return true on success, false on failure */
 63  bool
 64  IniFile::Open(const char *file)
 65  {
 66      char                        path[LINELEN] = "";
 67  
 68      if(IsOpen()) Close();
 69  
 70      TildeExpansion(file, path, sizeof(path));
 71  
 72      if((fp = fopen(path, "r")) == NULL)
 73          return(false);
 74  
 75      owned = true;
 76  
 77      if(!LockFile())
 78          return(false);
 79  
 80      return(true);
 81  }
 82  
 83  
 84  /*! Closes the file descriptor..
 85  
 86     @return true on success, false on failure */
 87  bool
 88  IniFile::Close()
 89  {
 90      int                         rVal = 0;
 91  
 92      if(fp != NULL){
 93          lock.l_type = F_UNLCK;
 94          fcntl(fileno(fp), F_SETLKW, &lock);
 95  
 96          if(owned)
 97              rVal = fclose(fp);
 98  
 99          fp = NULL;
100      }
101  
102      return(rVal == 0);
103  }
104  
105  
106  IniFile::ErrorCode
107  IniFile::Find(int *result, StrIntPair *pPair,
108       const char *tag, const char *section, int num, int *lineno)
109  {
110      const char                  *pStr;
111      int                         tmp;
112  
113      if((pStr = Find(tag, section, num)) == NULL){
114          // We really need an ErrorCode return from Find() and should be passing
115          // in a buffer. Just pick a suitable ErrorCode for now.
116  	if (lineno)
117  	    *lineno = 0;
118          return(ERR_TAG_NOT_FOUND);
119      }
120  
121      if(sscanf(pStr, "%i", &tmp) == 1){
122          *result = tmp;
123  	if (lineno)
124  	    *lineno = lineNo;
125          return(ERR_NONE);
126      }
127  
128      while(pPair->pStr != NULL){
129          if(strcasecmp(pStr, pPair->pStr) == 0){
130              *result = pPair->value;
131  	    if (lineno)
132  		*lineno = lineNo;
133              return(ERR_NONE);
134          }
135          pPair++;
136      }
137  
138      ThrowException(ERR_CONVERSION);
139      return(ERR_CONVERSION);
140  }
141  
142  
143  IniFile::ErrorCode
144  IniFile::Find(double *result, StrDoublePair *pPair,
145       const char *tag, const char *section, int num, int *lineno)
146  {
147      const char                  *pStr;
148      double                      tmp;
149  
150      if((pStr = Find(tag, section, num)) == NULL){
151          // We really need an ErrorCode return from Find() and should be passing
152          // in a buffer. Just pick a suitable ErrorCode for now.
153  	if (lineno)
154  	    *lineno = 0;
155          return(ERR_TAG_NOT_FOUND);
156      }
157  
158      if(sscanf(pStr, "%lf", &tmp) == 1){
159  	if (lineno)
160  	    *lineno = lineNo;
161          *result = tmp;
162  	if (lineno)
163  	    *lineno = lineNo;
164          return(ERR_NONE);
165      }
166  
167      while(pPair->pStr != NULL){
168          if(strcasecmp(pStr, pPair->pStr) == 0){
169              *result = pPair->value;
170  	    if (lineno)
171  		*lineno = lineNo;
172              return(ERR_NONE);
173          }
174          pPair++;
175      }
176  
177      ThrowException(ERR_CONVERSION);
178      return(ERR_CONVERSION);
179  }
180  
181  
182  /*! Finds the nth tag in section.
183  
184     @param tag Entry in the ini file to find.
185  
186     @param section The section to look for the tag.
187  
188     @param num (optionally) the Nth occurrence of the tag.
189  
190     @return pointer to the the variable after the '=' delimiter */
191  const char *
192  IniFile::Find(const char *_tag, const char *_section, int _num, int *lineno)
193  {
194      // WTF, return a pointer to the middle of a local buffer?
195      // FIX: this is totally non-reentrant.
196      static char                 line[LINELEN + 2] = "";        /* 1 for newline, 1 for NULL */
197      char                        bracketSection[LINELEN + 2] = "";
198      char                        *nonWhite;
199      int                         newLinePos;                /* position of newline to strip */
200      int                         len;
201      char                        tagEnd;
202      char                        *valueString;
203      char                        *endValueString;
204  
205      char  eline [(LINELEN + 2) * (MAX_EXTEND_LINES + 1)];
206      char* elineptr;
207      char* elinenext;
208      int   extend_ct = 0;
209  
210      // For exceptions.
211      lineNo = 0;
212      tag = _tag;
213      section = _section;
214      num = _num;
215  
216      /* check valid file */
217      if(!CheckIfOpen())
218          return(NULL);
219  
220      /* start from beginning */
221      rewind(fp);
222  
223      /* check for section first-- if it's non-NULL, then position file at
224         line after [section] */
225      if(section != NULL){
226          sprintf(bracketSection, "[%s]", section);
227  
228          /* find [section], and position fp just after it */
229          while (!feof(fp)) {
230  
231              if (NULL == fgets(line, LINELEN + 1, fp)) {
232                  /* got to end of file without finding it */
233                  ThrowException(ERR_SECTION_NOT_FOUND);
234                  return(NULL);
235              }
236  
237              if(check_line_endings(line)) {
238                  ThrowException(ERR_CONVERSION);
239                  return(NULL);
240              }
241  
242              /* got a line */
243              lineNo++;
244  
245              /* strip off newline */
246              newLinePos = strlen(line) - 1;        /* newline is on back from 0 */
247              if (newLinePos < 0) {
248                  newLinePos = 0;
249              }
250              if (line[newLinePos] == '\n') {
251                  line[newLinePos] = 0;        /* make the newline 0 */
252              }
253  
254              if (NULL == (nonWhite = SkipWhite(line))) {
255                  /* blank line-- skip */
256                  continue;
257              }
258  
259              /* not a blank line, and nonwhite is first char */
260              if (strncmp(bracketSection, nonWhite, strlen(bracketSection)) != 0){
261                  /* not on this line */
262                  continue;
263              }
264  
265              /* it matches-- fp is now set up for search on tag */
266              break;
267          }
268      }
269  
270      while (!feof(fp)) {
271          /* check for end of file */
272          if (NULL == fgets(line, LINELEN + 1, (FILE *) fp)) {
273              /* got to end of file without finding it */
274              ThrowException(ERR_TAG_NOT_FOUND);
275              return(NULL);
276          }
277  
278          if(check_line_endings(line)) {
279              ThrowException(ERR_CONVERSION);
280              return(NULL);
281          }
282  
283          /* got a line */
284          lineNo++;
285  
286          /* strip off newline */
287          newLinePos = strlen(line) - 1;        /* newline is on back from 0 */
288          if (newLinePos < 0) {
289              newLinePos = 0;
290          }
291          if (line[newLinePos] == '\n') {
292              line[newLinePos] = 0;        /* make the newline 0 */
293          }
294          // honor backslash (\) as line-end escape
295          if (newLinePos > 0 && line[newLinePos-1] == '\\') {
296             newLinePos = newLinePos-1;
297             line[newLinePos] = 0;
298             if (!extend_ct) {
299                 elineptr = (char*)eline; //first time
300                 strncpy(elineptr,line,newLinePos);
301                 elinenext = elineptr + newLinePos;
302             } else {
303                 strncpy(elinenext,line,newLinePos);
304                 elinenext = elinenext + newLinePos;
305             }
306             *elinenext = 0;
307             extend_ct++;
308             if (extend_ct > MAX_EXTEND_LINES) {
309                fprintf(stderr,
310                   "INIFILE lineno=%d:Too many backslash line extends (limit=%d)\n",
311                   lineNo, MAX_EXTEND_LINES);
312                ThrowException(ERR_OVER_EXTENDED);
313                return(NULL);
314             }
315             continue; // get next line to extend
316          } else {
317              if (extend_ct) {
318                 strncpy(elinenext,line,newLinePos);
319                 elinenext = elinenext + newLinePos;
320                 *elinenext = 0;
321              }
322          }
323          if (!extend_ct) {
324             elineptr = (char*)line;
325          }
326          extend_ct = 0;
327  
328          /* skip leading whitespace */
329          if (NULL == (nonWhite = SkipWhite(elineptr))) {
330              /* blank line-- skip */
331              continue;
332          }
333  
334          /* check for '[' char-- if so, it's a section tag, and we're out
335             of our section */
336          if (NULL != section && nonWhite[0] == '[') {
337              ThrowException(ERR_TAG_NOT_FOUND);
338              return(NULL);
339          }
340  
341          len = strlen(tag);
342          if (strncmp(tag, nonWhite, len) != 0) {
343              /* not on this line */
344              continue;
345          }
346  
347          /* it matches the first part of the string-- if whitespace or = is
348             next char then call it a match */
349          tagEnd = nonWhite[len];
350          if (tagEnd == ' ' || tagEnd == '\r' || tagEnd == '\t'
351              || tagEnd == '\n' || tagEnd == '=') {
352              /* it matches-- return string after =, or NULL */
353              if (--_num > 0) {
354                  /* Not looking for this one, so skip it... */
355                  continue;
356              }
357              nonWhite += len;
358              valueString = AfterEqual(nonWhite);
359              /* Eliminate white space at the end of a line also. */
360              if (NULL == valueString) {
361                  ThrowException(ERR_TAG_NOT_FOUND);
362                  return(NULL);
363              }
364              endValueString = valueString + strlen(valueString) - 1;
365              while (*endValueString == ' ' || *endValueString == '\t'
366                     || *endValueString == '\r') {
367                  *endValueString = 0;
368                  endValueString--;
369              }
370  	    if (lineno)
371  		*lineno = lineNo;
372              return(valueString);
373          }
374          /* else continue */
375      }
376  
377      ThrowException(ERR_TAG_NOT_FOUND);
378      return(NULL);
379  }
380  
381  const char *
382  IniFile::FindString(char *dest, size_t n, const char *_tag, const char *_section, int _num, int *lineno)
383  {
384      const char *res = Find(_tag, _section, _num, lineno);
385      if(res == NULL)
386          return res;
387      int r = snprintf(dest, n, "%s", res);
388      if(r < 0 || (size_t)r >= n) {
389          ThrowException(ERR_CONVERSION);
390          return NULL;
391      }
392      return dest;
393  }
394  
395  const char *
396  IniFile::FindPath(char *dest, size_t n, const char *_tag, const char *_section, int _num, int *lineno)
397  {
398      const char *res = Find(_tag, _section, _num, lineno);
399      if(!res)
400          return res;
401      if(TildeExpansion(res, dest, n)) {
402          return 0;
403      }
404      return dest;
405  }
406  
407  bool
408  IniFile::CheckIfOpen(void)
409  {
410      if(IsOpen())
411          return(true);
412  
413      ThrowException(ERR_NOT_OPEN);
414  
415      return(false);
416  }
417  
418  
419  bool
420  IniFile::LockFile(void)
421  {
422      lock.l_type = F_RDLCK;
423      lock.l_whence = SEEK_SET;
424      lock.l_start = 0;
425      lock.l_len = 0;
426  
427      if(fcntl(fileno(fp), F_SETLK, &lock) == -1){
428          if(owned)
429              fclose(fp);
430  
431          fp = NULL;
432          return(false);
433      }
434  
435      return(true);
436  }
437  
438  
439  /*! Expands the tilde to $(HOME) and concatenates file to it. If the first char
440      If file does not start with ~/, file will be copied into path as-is. 
441  
442     @param the input filename
443  
444     @param pointer for returning the resulting expanded name
445  
446   */
447  IniFile::ErrorCode
448  IniFile::TildeExpansion(const char *file, char *path, size_t size)
449  {
450      char                        *home;
451  
452      int res = snprintf(path, size, "%s", file);
453      if(res < 0 || (size_t)res >= size)
454          return ERR_CONVERSION;
455  
456      if (strlen(file) < 2 || !(file[0] == '~' && file[1] == '/')) {
457  	/* no tilde expansion required, or unsupported
458             tilde expansion type requested */
459  	return ERR_NONE;
460      }
461  
462      home = getenv("HOME");
463      if (!home) {
464          ThrowException(ERR_CONVERSION);
465  	return ERR_CONVERSION;
466      }
467  
468      res = snprintf(path, size, "%s%s", home, file + 1);
469      if(res < 0 || (size_t)res >= size) {
470          ThrowException(ERR_CONVERSION);
471          return ERR_CONVERSION;
472      }
473  
474      return ERR_NONE;
475  }
476  
477  int
478  TildeExpansion(const char *file, char *path, size_t size)
479  {
480      static IniFile f;
481      return !f.TildeExpansion(file, path, size);
482  }
483  
484  void
485  IniFile::ThrowException(ErrorCode errCode)
486  {
487      if(errCode & errMask){
488          exception.errCode = errCode;
489          exception.tag = tag;
490          exception.section = section;
491          exception.num = num;
492          exception.lineNo = lineNo;
493          throw(exception);
494      }
495  }
496  
497  
498  /*! Ignoring any tabs, spaces or other white spaces, finds the first
499     character after the '=' delimiter.
500  
501     @param string Pointer to the tag
502  
503     @return NULL or pointer to first non-white char after the delimiter
504  
505     Called By: find() and section() only. */
506  char *
507  IniFile::AfterEqual(const char *string)
508  {
509      const char                  *spot = string;
510  
511      for (;;) {
512          if (*spot == '=') {
513              /* = is there-- return next non-white, or NULL if not there */
514              for (;;) {
515                  spot++;
516                  if (0 == *spot) {
517                      /* ran out */
518                      return(NULL);
519                  } else if (*spot != ' ' && *spot != '\t' && *spot != '\r'
520                             && *spot != '\n') {
521                      /* matched! */
522                      return((char *)spot);
523                  } else {
524                      /* keep going for the text */
525                      continue;
526                  }
527              }
528          } else if (*spot == 0) {
529              /* end of string */
530              return(NULL);
531          } else {
532              /* haven't seen '=' yet-- keep going */
533              spot++;
534              continue;
535          }
536      }
537  }
538  
539  
540  /*! Finds the first non-white character on a new line and returns a
541     pointer. Ignores any line that starts with a comment char i.e. a ';' or 
542     '#'.
543  
544     @return NULL if not found or a valid pointer.
545  
546     Called By: find() and section() only. */
547  char *
548  IniFile::SkipWhite(const char *string)
549  {
550      while(true){
551          if (*string == 0) {
552              return(NULL);
553          }
554  
555          if ((*string == ';') || (*string == '#')) {
556              return(NULL);
557          }
558  
559          if (*string != ' ' && *string != '\t' && *string != '\r'
560              && *string != '\n') {
561              return((char *)string);
562          }
563  
564          string++;
565      }
566  }
567  
568  
569  void
570  IniFile::Exception::Print(FILE *fp)
571  {
572      const char                  *msg;
573  
574      switch(errCode){
575      case ERR_NONE:
576          msg = "ERR_NONE";
577          break;
578  
579      case ERR_NOT_OPEN:
580          msg = "ERR_NOT_OPEN";
581          break;
582  
583      case ERR_SECTION_NOT_FOUND:
584          msg = "ERR_SECTION_NOT_FOUND";
585          break;
586  
587      case ERR_TAG_NOT_FOUND:
588          msg = "ERR_TAG_NOT_FOUND";
589          break;
590  
591      case ERR_CONVERSION:
592          msg = "ERR_CONVERSION";
593          break;
594  
595      case ERR_LIMITS:
596          msg = "ERR_LIMITS";
597          break;
598  
599      case ERR_OVER_EXTENDED:
600          msg = "ERR_OVER_EXTENDED";
601          break;
602  
603      default:
604          msg = "UNKNOWN";
605      }
606  
607      fprintf(fp, "INIFILE: %s, section=%s, tag=%s, num=%d, lineNo=%d\n",
608              msg, section, tag, num, lineNo);
609  }
610  
611  
612  extern "C" const char *
613  iniFind(FILE *fp, const char *tag, const char *section)
614  {
615      IniFile                     f(false, fp);
616  
617      return(f.Find(tag, section));
618  }
619  
620  extern "C" const int
621  iniFindInt(FILE *fp, const char *tag, const char *section, int *result)
622  {
623      IniFile f(false, fp);
624      return(f.Find(result, tag, section));
625  }
626  
627  extern "C" const int
628  iniFindDouble(FILE *fp, const char *tag, const char *section, double *result)
629  {
630      IniFile f(false, fp);
631      return(f.Find(result, tag, section));
632  }