text: /****************************************************************************
   * apps/fsutils/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 <strings.h>
  #include <debug.h>
  
  #include "fsutils/inifile.h"
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  /* The maximum size of a line in the INI file */
  
  #ifndef CONFIG_FSUTILS_INIFILE_MAXLINE
  #  define CONFIG_FSUTILS_INIFILE_MAXLINE 256
  #endif
  
  #ifndef CONFIG_FSUTILS_INIFILE_DEBUGLEVEL
  #  define CONFIG_FSUTILS_INIFILE_DEBUGLEVEL 0
  #endif
  
  #ifdef CONFIG_CPP_HAVE_VARARGS
  #  if CONFIG_FSUTILS_INIFILE_DEBUGLEVEL > 0
  #    define inidbg(format, ...) \
         printf(EXTRA_FMT format EXTRA_ARG, ##__VA_ARGS__)
  #  else
  #    define inidbg(x...)
  #  endif
  
  #  if CONFIG_FSUTILS_INIFILE_DEBUGLEVEL > 1
  #    define iniinfo(format, ...) \
         printf(EXTRA_FMT format EXTRA_ARG, ##__VA_ARGS__)
  #  else
  #    define iniinfo(x...)
  #  endif
  #else
  #  if CONFIG_FSUTILS_INIFILE_DEBUGLEVEL > 0
  #    define inidbg printf
  #  else
  #    define inidbg (void)
  #  endif
  
  #  if CONFIG_FSUTILS_INIFILE_DEBUGLEVEL > 1
  #    define iniinfo printf
  #  else
  #    define iniinfo (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_FSUTILS_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_FSUTILS_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/
     */
  
    iniinfo("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)
          {
            iniinfo("Returning NULL\n");
            return NULL;
          }
  
        iniinfo("varinfo.variable=\"%s\"\n", varinfo.variable);
  
        /* Does the variable name match the one we are looking for? */
  
        if (strcasecmp(varinfo.variable, variable) == 0)
          {
            /* Yes... then we have it! */
  
            iniinfo("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;
  
    iniinfo("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);
        iniinfo("variable_value=0x%p\n", value);
  
        if (value && *value)
          {
            iniinfo("variable_value=\"%s\"\n", value);
            ret = value;
          }
      }
  
    /* Return the string that we found. */
  
    iniinfo("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 */
  
    iniinfo("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.
         */
  
        iniinfo("%s=\"%s\"\n", variable, value);
        ret = strtol(value, NULL, 0);
      }
  
    /* Return the value that we found. */
  
    iniinfo("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);
      }
  }
  