summaryrefslogtreecommitdiff
path: root/apps/system/inifile/inifile.c
blob: a7bb8c0b14a920a1f39e50bc03d59298fbe7458e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
/****************************************************************************
 * apps/system/inifile/inifile.c
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <debug.h>

#include <apps/inifile.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* The maximum size of a line in the INI file */

#ifndef CONFIG_SYSTEM_INIFILE_MAXLINE
#  define CONFIG_SYSTEM_INIFILE_MAXLINE 256
#endif

#ifndef CONFIG_SYSTEM_INIFILE_DEBUGLEVEL
#  define CONFIG_SYSTEM_INIFILE_DEBUGLEVEL 0
#endif

#ifdef CONFIG_CPP_HAVE_VARARGS
#  if CONFIG_SYSTEM_INIFILE_DEBUGLEVEL > 0
#    define inidbg(format, ...) \
       printf(EXTRA_FMT format EXTRA_ARG, ##__VA_ARGS__)
#  else
#    define inidbg(x...)
#  endif

#  if CONFIG_SYSTEM_INIFILE_DEBUGLEVEL > 1
#    define inivdbg(format, ...) \
       printf(EXTRA_FMT format EXTRA_ARG, ##__VA_ARGS__)
#  else
#    define inivdbg(x...)
#  endif
#else
#  if CONFIG_SYSTEM_INIFILE_DEBUGLEVEL > 0
#    define inidbg printf
#  else
#    define inidbg (void)
#  endif

#  if CONFIG_SYSTEM_INIFILE_DEBUGLEVEL > 1
#    define inivdbg printf
#  else
#    define inivdbg (void)
#  endif
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* A structure that describes one entry from the INI file */

struct inifile_var_s
{
  FAR char *variable;
  FAR char *value;
};

/* This structure describes the state of one instance of the INI file parser */

struct inifile_state_s
{
  FILE *instream;
  int   nextch;
  char  line[CONFIG_SYSTEM_INIFILE_MAXLINE+1];
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static bool inifile_next_line(FAR struct inifile_state_s *priv);
static int  inifile_read_line(FAR struct inifile_state_s *priv);
static int  inifile_read_noncomment_line(FAR struct inifile_state_s *priv);
static bool inifile_seek_to_section(FAR struct inifile_state_s *priv,
              FAR const char *section);
static bool inifile_read_variable(FAR struct inifile_state_s *priv,
              FAR struct inifile_var_s *varinfo);
static FAR char *
            inifile_find_section_variable(FAR struct inifile_state_s *priv,
              FAR const char *variable);
static FAR char *
            inifile_find_variable(FAR struct inifile_state_s *priv,
              FAR const char *section, FAR const char *variable);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name:  inifile_next_line
 *
 * Description:
 *   Skip to the first character of the next line.  Returns true if the end
 *   of file was not encountered.
 *
 ****************************************************************************/

static bool inifile_next_line(FAR struct inifile_state_s *priv)
{
  /* Search ahead for the end of line mark (or possibly the end of file mark) */

  while ((priv->nextch != '\n') && (priv->nextch != EOF))
    {
      priv->nextch = getc(priv->instream);
    }

  /* Re-prime the pump with the first character from the next line.  NOTE:
   * this logic depends on the fact that getc() will return EOF repeatedly.
   */

  priv->nextch = getc(priv->instream);
  return (priv->nextch != EOF);
}

/****************************************************************************
 * Name:  inifile_read_line
 *
 * Description:
 *   Read the next line from the INI file into the line buffer and return
 *   the number of characters read into the buffer. If we hit the end of a
 *   section (or the end of a file), this function will return a count of
 *   zero.
 *
 ****************************************************************************/

static int inifile_read_line(FAR struct inifile_state_s *priv)
{
  int nbytes;

  /* Assuming that the file pointer is correctly positioned at the beginning
   * of the next line, read until the end of line indication is found (or
   * until the line buffer is full).  This is basically fgets().
   */

  nbytes = 0;
  while ((nbytes < CONFIG_SYSTEM_INIFILE_MAXLINE) &&
         (priv->nextch != EOF) &&
         (priv->nextch != '\n'))
   {
      /* Always ignore carriage returns */

      if (priv->nextch != '\r')
        {
          /* Ignore any leading whitespace on the line */

          if (nbytes || (priv->nextch != ' ' && priv->nextch != '\t'))
            {
              /* Add the new character to the line buffer */

              priv->line[nbytes] = priv->nextch;
              nbytes++;
            }
        }

      /* Get the next character from the INI file */

      priv->nextch = getc(priv->instream);
    }

  /* NUL terminate the string */

  priv->line[nbytes] = '\0';

  /* Skip to the first character of the next line.  This should normally
   * just amount to skipping over the newline, but could be more involved
   * if we had to truncate the line to fit into the line buffer.
   */

  if (priv->nextch != EOF)
    {
      (void)inifile_next_line(priv);
    }

  /* And return the number of bytes read (excluding the NUL terminator and
   * leading whitespace).
   */

  return nbytes;
}

/****************************************************************************
 * Name:  inifile_read_noncomment_line
 *
 * Description:
 *   Read until either a (1) no further lines are found in the file, or (2)
 *   a line that does not begin with a semi-colon is found
 *
 ****************************************************************************/

static int inifile_read_noncomment_line(FAR struct inifile_state_s *priv)
{
  int nbytes;

  /* Read until either a (1) no further lines are found in
   * the file, or (2) a line that does not begin with a semi-colon
   * is found */

  do nbytes = inifile_read_line(priv);
  while (nbytes > 0 && priv->line[0] == ';');

  return nbytes;
}

/****************************************************************************
 * Name:  inifile_seek_to_section
 *
 * Description:
 *   Positions the file pointer to the line containing the first variable
 *   description within the INI file. Returns 1 if the section was found.
 *   In this case, the file pointer will be positioned at the beginning of
 *   the first variable line.
 *
 ****************************************************************************/

static bool inifile_seek_to_section(FAR struct inifile_state_s *priv,
                                    FAR const char *section)
{
  int nbytes;

  /* Rewind to the beginning of the INI file and re-prime the pump with the
   * first character from the INI file.
   */

  rewind(priv->instream);
  priv->nextch = getc(priv->instream);

  /* Loop until either the section is found, or until we hit the end of the
   * INI file.
   */

  do
    {
      /* Read the next line into the input buffer.  A returned value of zero
       * bytes means nothing special here -- could be EOF or a blank line.
       */

      nbytes = inifile_read_noncomment_line(priv);

      /* It takes at least three bytes of data to be a candidate for a
       * section header.
       */

      if (nbytes >= 3)
        {
          /* A section header must begin with a left bracket */

          if (priv->line[0] == '[')
            {
              /* The section name should start with the first character
               * after the left bracket.
               */

              FAR char *sectend;

              /* The section name should extend to the right bracket. */

              sectend = strchr(&priv->line[1], ']');

              /* Replace the right bracket (if found) with a NULL
               * terminator.
               */

              if (sectend)
                {
                  *sectend = '\0';
                }

              /* Then compare the section name to the one we are looking for */

              if (strcasecmp(&priv->line[1], section) == 0)
                {
                  /* The section names match!  Return success */

                  return true;
                }
            }
        }
    }
  while (priv->nextch != EOF);

  /* If we got here, we search the whole INI file without finding
   * the requested section
   */

  inidbg("ERROR: Section \"%s\" not found\n", section);
  return false;
}

/****************************************************************************
 * Name:  inifile_read_variable
 *
 * Description:
 *   Obtain variable info from the next line in the section.  This assumes
 *   that the file pointer is pointing to the beginning of the next line.
 *   If there is no further data in the section, false is returned.
 *
 ****************************************************************************/

static bool inifile_read_variable(FAR struct inifile_state_s *priv,
                                  FAR struct inifile_var_s *varinfo)
{
  FAR char *ptr;

  /* Read until either (1) the end of file is found, (2) the end of
   * the section is found, or (3) a valid variable assignment is found.
   */

  for (;;)
    {
      /* Read the next line in the buffer */

      int nbytes = inifile_read_noncomment_line(priv);

      /* Make sure that the line is non-NULL and that this is not the
       * beginning of a new section
       */

      if (!nbytes || priv->line[0] == '[')
       {
         return false;
       }

      /* Search for the '=' delimiter.  NOTE  the line is guaranteed to
       * be NULL terminated by inifile_read_noncomment_line().
       */

      ptr = strchr(&priv->line[1], '=');

      /* If the delimiter was found, return success */

      if (ptr)
        {
          /* Put NUL termination between the variable name and the
           * variable value (replacing the equal sign).
           */

          *ptr = '\0';

          /* Set up the return structure.  NOTE:  value may point at
           * a NULL string
           */

          varinfo->variable = (char*)priv->line;
          varinfo->value    = (ptr + 1);
          return true;
        }
    }
}

/****************************************************************************
 * Name:  inifile_find_section_variable
 *
 * Description:
 *   Find the value string associated with the variable name.  This function
 *   will return NULL on failure to find the variable.  It will return a
 *   pointer to an empty string is the variable is found, but not assigned a
 *   value.
 *
 ****************************************************************************/

static FAR char *
  inifile_find_section_variable(FAR struct inifile_state_s *priv,
    FAR const char *variable)
{
  /* Loop until either (1) we hit the end of file, (2) we hit the end
   * of the section, or (3) we find the variable that we are looking
   * for/
   */

  inivdbg("variable=\"%s\"\n", variable);

  for (;;)
    {
      /* Get the next variable from this section. */

      struct inifile_var_s varinfo;
      bool found = inifile_read_variable(priv, &varinfo);

      /* Is there anything left in the section? */

      if (!found)
        {
          inivdbg("Returning NULL\n");
          return NULL;
        }

      inivdbg("varinfo.variable=\"%s\"\n", varinfo.variable);

      /* Does the the variable name match the one we are looking for? */

      if (strcasecmp(varinfo.variable, variable) == 0)
        {
          /* Yes... then we have it! */

          inivdbg("Returning \"%s\"\n", varinfo.value);
          return varinfo.value;
        }
    }
}

/****************************************************************************
 * Name:  inifile_find_variable
 *
 * Description:
 *   Obtains the specified string value for the specified variable name
 *   within the specified section of the INI file.
 *
 ****************************************************************************/

static FAR char *inifile_find_variable(FAR struct inifile_state_s *priv,
                                       FAR const char *section,
                                       FAR const char *variable)
{
  FAR char *ret = NULL;

  inivdbg("section=\"%s\" variable=\"%s\"\n", section, variable);

  /* Seek to the first variable in the specified section of the INI file */

  if (priv->instream && inifile_seek_to_section(priv, section))
    {
      /* If the seek was successful, then find the value string within
       * the section
       */

      FAR char *value = inifile_find_section_variable(priv, variable);
      inivdbg("variable_value=0x%p\n", value);

      if (value && *value)
        {
          inivdbg("variable_value=\"%s\"\n", value);
          ret = value;
        }
    }

  /* Return the string that we found. */

  inivdbg("Returning 0x%p\n", ret);
  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name:  inifile_initialize
 *
 * Description:
 *   Initialize for access to the INI file 'inifile_name'
 *
 ****************************************************************************/

INIHANDLE inifile_initialize(FAR const char *inifile_name)
{
  /* Allocate an INI file parser state structure */

  FAR struct inifile_state_s *priv =
    (FAR struct inifile_state_s *)malloc(sizeof(struct inifile_state_s));

  if (!priv)
    {
      inidbg("ERROR: Failed to allocate state structure\n");
      return (INIHANDLE)NULL;
    }

  /* Open the specified INI file for reading */

  priv->instream = fopen(inifile_name, "r");

  /* Prime the pump */

  if (priv->instream)
    {
      priv->nextch = getc(priv->instream);
      return (INIHANDLE)priv;
    }
  else
    {
      inidbg("ERROR: Could not open \"%s\"\n", inifile_name);
      return (INIHANDLE)NULL;
    }
}

/****************************************************************************
 * Name:  inifile_uninitialize
 *
 * Description:
 *   Free resources commit to INI file parsing
 *
 ****************************************************************************/

void inifile_uninitialize(INIHANDLE handle)
{
  FAR struct inifile_state_s *priv = (FAR struct inifile_state_s *)handle;

  if (priv)
    {
      /* Close the INI file stream */

      if (priv->instream)
        {
          fclose(priv->instream);
        }

      /* Release the state structure */

      free(priv);
    }
}

/****************************************************************************
 * Name: inifile_read_string
 *
 * Description:
 *   Obtains the specified string value for the specified variable name
 *   within the specified section of the INI file.  The receiver of the
 *   value string should call inifile_free_string when it no longer needs
 *   the memory held by the value string.
 *
 ****************************************************************************/

FAR char *inifile_read_string(INIHANDLE handle,
                              FAR const char *section,
                              FAR const char *variable,
                              FAR const char *defvalue)
{
  FAR struct inifile_state_s *priv = (FAR struct inifile_state_s *)handle;
  FAR char *ret = NULL;
  FAR const char *value;

  /* Get a reference to the volatile version of the string */

  value = inifile_find_variable(priv, section, variable);

  /* If the variable was not found, then use the default value */

  if (!value)
    {
      /* Selecting the default string */

      value = defvalue;
    }

  /* If this was successful, create a non-volatile copy of the string
   * We do this even if the default value is used because the caller
   * will (eventually) deallocate it.
   */

  if (value)
    {
      ret = strdup(value);
    }

  /* Return the string that we found. */

  return ret;
}

/****************************************************************************
 * Name:  inifile_read_integer
 *
 * Description:
 *   Obtains the specified integer value for the specified variable name
 *   within the specified section of the INI file
 *
 ****************************************************************************/

long inifile_read_integer(INIHANDLE handle,
                          FAR const char *section,
                          FAR const char *variable,
                          FAR long defvalue)
{
  FAR struct inifile_state_s *priv = (FAR struct inifile_state_s *)handle;
  FAR char *value;
  long ret = defvalue;

  /* Assume failure to find the requested value */

  inivdbg("section=\"%s\" variable=\"%s\" defvalue=%d\n",
          section, variable, defvalue);

  /* Get the value as a string first */

  value = inifile_find_variable(priv, section, variable);

  /* If this was successful, then convert the string to an integer value. */

  if (value)
    {
      /* Then convert the string to an integer value (accept any base, and
       * ignore all conversion errors.
       */

      inivdbg("%s=\"%s\"\n", variable, value);
      ret = strtol(value, NULL, 0);
    }

  /* Return the value that we found. */

  inivdbg("Returning %d\n", ret);
  return ret;
}

/****************************************************************************
 * Name:  inifile_free_string
 *
 * Description:
 *   Release resources allocated for the value string previously obtained
 *   from inifile_read_string.  The purpose of this inline function is to
 *   hide the memory allocator used by this implementation.
 *
 ****************************************************************************/

void inifile_free_string(FAR char *value)
{
  if (value)
    {
      free(value);
    }
}