/**
 * @file cfgfile.cc
 *
 * ConfigFile - Class for maintaining a configuration file similar to
 * INI-files.
 *
 * Copyright: 2005 Thomas Wintergerst. All rights reserved.
 *
 * $Id: cfgfile.cc,v 1.2.2.1 2006/02/28 21:35:44 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     Common source files for c4b tools $
 * @date        06.05.2005
 * @author      "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 *
 * </p>
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

// Own interface
#include "cfgfile.h"

// System includes
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <assert.h>

// Import includes

#define __CFGFILE__

// Local includes





// === Private definitions ===============================================





// === Definitions for public declarations ===============================





// === Prototypes for private functions ==================================





// === Implementation of class members ===================================





// --- Class for handling a section within the database ------------------

/**
 * Get the short name of the section (last path component).
 */

const std::string &CConfigFile::CSection::GetShortName (void) const
{
   return (m_strShortName);
} // CConfigFile::CSection::GetShortName





/**
 * Get the full path of the section.
 */

const std::string &CConfigFile::CSection::GetFullName (void) const
{
   return (m_strFullName);
} // CConfigFile::CSection::GetFullName





/**
 * Get the comment assigned to the section.
 */

const std::string &CConfigFile::CSection::GetComment (void) const
{
   return (m_strComment);
} // CConfigFile::CSection::GetComment





/**
 * Set the comment assigned to the section.
 */

void CConfigFile::CSection::SetComment
   (const std::string &str)
{
   m_strComment = str;
   m_fModified = true;
   
} // CConfigFile::CSection::SetComment





/**
 * Check existing value.
 */

bool CConfigFile::CSection::KeyExists
   (const std::string &strKey) const
{
   CKeyValueMap::const_iterator iterMap;
   
   iterMap = m_mapKV.find (strKey);
   if (iterMap == m_mapKV.end ())
   {
      return (false);
   }
   return (true);
} // CConfigFile::CSection::KeyExists





/**
 * Get the value for a given key as a string.
 */

std::string CConfigFile::CSection::GetKeyValueAsString
   (const std::string &strKey) const
{
   CKeyValueMap::const_iterator iterMap;
   
   iterMap = m_mapKV.find (strKey);
   if (iterMap == m_mapKV.end ())
   {
      // key not found --> return empty string
      return (std::string (""));
   }
   // key found --> return the associated value as a string
   return (iterMap->second->m_strValue);
} // CConfigFile::CSection::GetKeyValueAsString





/**
 * Get the comment assigned to a given key.
 */

std::string CConfigFile::CSection::GetKeyComment
   (const std::string &strKey) const
{
   CKeyValueMap::const_iterator iterMap;
   
   iterMap = m_mapKV.find (strKey);
   if (iterMap == m_mapKV.end ())
   {
      // key not found --> return empty string
      return (std::string (""));
   }
   // key found --> return the associated comment
   return (iterMap->second->m_strComment);
} // CConfigFile::CSection::GetKeyComment





/**
 * Add or update a key/value pair for this section.
 */
template<>
void CConfigFile::CSection::AddKeyValuePair<const std::string &>
   (const std::string &strKey,
    const std::string &strValue,
    const std::string &strComment)
{
   Value_t *pKV;

   pKV = new Value_t (strKey, strValue, strComment);
   if (m_pFirstValue == NULL)
   {
      m_pFirstValue = pKV;
   }
   if (m_pLastValue != NULL)
   {
      m_pLastValue->m_pNext = pKV;
   }
   pKV->m_pPrev = m_pLastValue;
   pKV->m_pNext = NULL;
   m_pLastValue = pKV;
   
   m_fModified = true;
   
   (void) m_mapKV.insert (CKeyValueMap::value_type (strKey, pKV));

} // CConfigFile::CSection::AddKeyValuePair<const std::string &>





/**
 * Constructor.
 */

CConfigFile::CSection::CSection
   (CSection          *pParent,
    bool              &fModified,
    const std::string &strFullName,
    const std::string &strShortName,
    const std::string &strComment):
   m_pParent (pParent),
   m_fModified (fModified),
   m_strFullName (strFullName),
   m_strShortName (strShortName),
   m_strComment (strComment),
   m_pPrev (NULL),
   m_pNext (NULL),
   m_pFirstSub (NULL),
   m_pLastSub (NULL),
   m_pFirstValue (NULL),
   m_pLastValue (NULL)
{
   // nothing more to do
   
} // CConfigFile::CSection::CSection





// --- The type of an iterator over the sections of a configuration database ---

/**
 * Default constructor to create an iterator pointing nowhere.
 */

CConfigFile::CSectionIterator::CSectionIterator (void):
   m_strParent (),
   m_pMap (NULL),
   m_iterMap ()
{
   // nothing more to do
   
} // CConfigFile::CSectionIterator::CSectionIterator





/**
 * Copy constructor.
 */

CConfigFile::CSectionIterator::CSectionIterator
   (const CSectionIterator &arg):
   m_strParent (arg.m_strParent),
   m_pMap (arg.m_pMap),
   m_iterMap (arg.m_iterMap)
{
   // nothing more to do
   
} // CConfigFile::CSectionIterator::CSectionIterator





/**
 * Dereferencing operator for the current section (pointer).
 */

CConfigFile::CSection * CConfigFile::CSectionIterator::operator-> (void)
{
   if (m_pMap == NULL)
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator->: Iterator not initialised");
   }
   if (m_iterMap == m_pMap->end ())
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator->: Iterator does not point to valid section");
   }
   
   return (m_iterMap->second);
} // CConfigFile::CSectionIterator::operator->





/**
 * Dereferencing operator for the current section (reference).
 */

CConfigFile::CSection & CConfigFile::CSectionIterator::operator* (void)
{
   if (m_pMap == NULL)
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator*: Iterator not initialised");
   }
   if (m_iterMap == m_pMap->end ())
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator*: Iterator does not point to valid section");
   }
   
   return (*(m_iterMap->second));
} // CConfigFile::CSectionIterator::operator*





/**
 * Postfix-Increment operator.
 *
 * The next iterator position can either be directly the next
 * config section or the next section belonging to our parent
 * section. The first case matches the situation for an iteror over
 * all available sections. The later is true, if this iterator was
 * constructed to enumerate only the sub-sections of a parent
 * section. Then we must check if the directly next section still
 * belongs to our parent. If it does not, we have reached the end
 * of the enumeration and must set the internal map iterator to the
 * end of the map.
 */

CConfigFile::CSectionIterator CConfigFile::CSectionIterator::operator++ (void)
{
   if (m_pMap == NULL)
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator++: Iterator not initialised");
   }
   if (m_iterMap == m_pMap->end ())
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator++: Attempt to advance behind end of sequence");
   }
   
   // save the current members to return an iterator with these settings
   CSectionIterator copy = *this;
   
   // loop to find the next section, if requested with the same parent and on
   // the same layer
   for (;;)
   {
      // advance the map iterator to the next section
      ++m_iterMap;

      // if this is an iterator for enumerating a specific sub-section set, we
      // must check if we just left our parent section
      if (m_iterMap != m_pMap->end () && m_strParent.length () != 0)
      {
         if (m_iterMap->first.find (m_strParent) == std::string::npos)
         {
            // the parent section name is not part of the current full section
            // name any more --> we left the sequence and must proceed to the end
            // of the map
            m_iterMap = m_pMap->end ();
            break;
         }
         if (m_iterMap->first.find ('/', m_strParent.length () + 1) !=
                std::string::npos)
         {
            // the current full section name addresses a sub-section below the
            // requested layer directly under the parent section --> we are too
            // deep and must advance to the next section to get higher again
            continue;
         }
      }
      // not very nice, but if we get here we must leave the loop
      break;
   }

   return (copy);
} // CConfigFile::CSectionIterator::operator++





/**
 * Prefix-Increment operator.
 *
 * The next iterator position can either be directly the next
 * config section or the next section belonging to our parent
 * section. The first case matches the situation for an iteror over
 * all available sections. The later is true, if this iterator was
 * constructed to enumerate only the sub-sections of a parent
 * section. Then we must check if the directly next section still
 * belongs to our parent. If it does not, we have reached the end
 * of the enumeration and must set the internal map iterator to the
 * end of the map.
 */

CConfigFile::CSectionIterator CConfigFile::CSectionIterator::operator++ (int)
{
   if (m_pMap == NULL)
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator++: Iterator not initialised");
   }
   if (m_iterMap == m_pMap->end ())
   {
      throw std::runtime_error
               ("CConfigFile::CSectionIterator::operator++: Attempt to advance behind end of sequence");
   }
   
   // loop to find the next section, if requested with the same parent and on
   // the same layer
   for (;;)
   {
      // advance the map iterator to the next section
      ++m_iterMap;

      // if this is an iterator for enumerating a specific sub-section set, we
      // must check if we just left our parent section
      if (m_iterMap != m_pMap->end () && m_strParent.length () != 0)
      {
         if (m_iterMap->first.find (m_strParent) == std::string::npos)
         {
            // the parent section name is not part of the current full section
            // name any more --> we left the sequence and must proceed to the end
            // of the map
            m_iterMap = m_pMap->end ();
            break;
         }
         if (m_iterMap->first.find ('/', m_strParent.length () + 1) !=
                std::string::npos)
         {
            // the current full section name addresses a sub-section below the
            // requested layer directly under the parent section --> we are too
            // deep and must advance to the next section to get higher again
            continue;
         }
      }
      // not very nice, but if we get here we must leave the loop
      break;
   }

   return (*this);
} // CConfigFile::CSectionIterator::operator++





/**
 * Operator to compare for equality.
 */

bool CConfigFile::CSectionIterator::operator==
   (const CSectionIterator &arg)
{
   return (m_pMap != NULL &&
           m_pMap == arg.m_pMap &&
           m_iterMap == arg.m_iterMap);
} // CConfigFile::CSectionIterator::operator==





/**
 * Operator to compare for inequality.
 */

bool CConfigFile::CSectionIterator::operator!=
   (const CSectionIterator &arg)
{
   return (m_pMap == NULL ||
           m_pMap != arg.m_pMap ||
           m_iterMap != arg.m_iterMap);
} // CConfigFile::CSectionIterator::operator!=





/**
 * Assignment operator.
 */

CConfigFile::CSectionIterator & CConfigFile::CSectionIterator::operator=
   (const CSectionIterator &arg)
{
   m_strParent = arg.m_strParent;
   m_pMap      = arg.m_pMap;
   m_iterMap   = arg.m_iterMap;
   
   return (*this);
} // CConfigFile::CSectionIterator::operator=





/**
 * Constructor to create an iterator pointing to a valid element.
 */

CConfigFile::CSectionIterator::CSectionIterator
   (const std::string                                      &strParent,
    std::map<std::string, value_type *, CIcLess>           *pMap,
    std::map<std::string, value_type *, CIcLess>::iterator  iterCurr):
   m_strParent (strParent),
   m_pMap (pMap),
   m_iterMap (iterCurr)
{
   // nothing more to do
   
} // CConfigFile::CSectionIterator::CSectionIterator





// --- Class for maintaining a configuration file similar to INI-files ---

/**
 * Constructor for an empty configuration database.
 */

CConfigFile::CConfigFile (void):
   m_strFileName (),
   m_fModified (false),
   m_mapDb (),
   m_pTopSect (NULL)
{
   // the top-level section does always exist, but will never occur in a
   // configuration file
   m_pTopSect = new CSection (NULL, m_fModified, "/", "", "");
   m_mapDb.insert (CSectionMap::value_type ("/", m_pTopSect));
   
} // CConfigFile::CConfigFile





/**
 * Constructor from an existing configuration file.
 */

CConfigFile::CConfigFile
   (const std::string &strFileName):
   m_strFileName (strFileName),
   m_fModified (false),
   m_mapDb (),
   m_pTopSect (NULL)
{
   // the top-level section does always exist, but will never occur in a
   // configuration file
   m_pTopSect = new CSection (NULL, m_fModified, "/", "", "");
   m_mapDb.insert (CSectionMap::value_type ("/", m_pTopSect));
   
   ReadFromFileInternal (m_strFileName);
   m_fModified = false;
   
} // CConfigFile::CConfigFile





/**
 * Destructor.
 */

CConfigFile::~CConfigFile (void)
{
   // remove all existing configuration sections
   RemoveSection (m_pTopSect);
   
   // the top-level section will not really be deleted by the former call,
   // delete it now
   m_mapDb.clear ();
   delete m_pTopSect;
   m_pTopSect = NULL;
   
} // CConfigFile::~CConfigFile





/**
 * Get the name of the underlying configuration file.
 */

const std::string &CConfigFile::GetFileName (void) const
{
   return (m_strFileName);
} // CConfigFile::GetFileName





/**
 * Get the current number of section of the database.
 */

size_t CConfigFile::GetNumSections (void) const
{
   return (m_mapDb.size ());
} // CConfigFile::GetNumSections





/**
 * Get an iterator to the beginning of a section hierarchy layer.
 */

CConfigFile::CSectionIterator CConfigFile::SectionBegin
   (const std::string &strBase)
{
   if (strBase.length () == 0)
   {
      // walk through all sections regardless of the hierarchy --> simply
      // return an iterator to the start of our section map
      return (CSectionIterator (std::string (), &m_mapDb, m_mapDb.begin ()));
   }
   
   // enumerate a single layer for a parent section --> find the parent section
   // and create an iterator for its sub-sections
   
   // if the given section name does not start with a path separator, add one
   std::string strParent = strBase;
   if (strParent [0] != '/')
   {
      strParent.insert (0, "/");
   }
   // Note: One could try to eliminate all doubled path separators from the
   //       parent section name. But it should be sufficient to have the
   //       caller specify a correct string.
   
   CSectionMap::iterator iterMap;
   iterMap = m_mapDb.find (strParent);
   if (iterMap == m_mapDb.end ())
   {
      // parent section not found --> just return an iterator behind the end
      return (CSectionIterator (strParent, &m_mapDb, m_mapDb.end ()));
   }
   
   // the parent section exists --> its sub-sections must start directly after
   // the parent section, if there are any
   ++iterMap;
   if (iterMap == m_mapDb.end ())
   {
      // parent section is the last section of the database --> return end
      // iterator
      return (CSectionIterator (strParent, &m_mapDb, m_mapDb.end ()));
   }
   if (iterMap->first.find (strParent) == std::string::npos)
   {
      // parent section has no sub-sections --> return end iterator
      return (CSectionIterator (strParent, &m_mapDb, m_mapDb.end ()));
   }
   
   // so now we have a valid iterator to a sub-section of the given parent
   // section --> return a section iterator to it
   return (CSectionIterator (strParent, &m_mapDb, iterMap));
} // CConfigFile::SectionBegin





/**
 * Get an iterator behind the end of any section hierarchy layer.
 */

CConfigFile::CSectionIterator CConfigFile::SectionEnd (void)
{
   return (CSectionIterator (std::string (), &m_mapDb, m_mapDb.end ()));
} // CConfigFile::SectionEnd





/**
 * Find a specific section through its name.
 */

CConfigFile::CSectionIterator CConfigFile::SectionFind
   (const std::string &strSectionName)
{
   // if the given section name does not start with a path separator, add one
   std::string strTmp = strSectionName;
   if (strTmp.length () == 0 || strTmp [0] != '/')
   {
      strTmp.insert (0, "/");
   }
   // Note: One could try to eliminate all doubled path separators from the
   //       section name. But it should be sufficient to have the caller
   //       specify a correct string.
   
   CSectionMap::iterator iterMap;
   iterMap = m_mapDb.find (strTmp);
   if (iterMap == m_mapDb.end ())
   {
      // section not found --> just return an iterator behind the end
      return (CSectionIterator (strTmp, &m_mapDb, m_mapDb.end ()));
   }
   
   // section found --> return a section iterator to it
   return (CSectionIterator (strTmp, &m_mapDb, iterMap));
} // CConfigFile::SectionFind





/**
 * Add a new section to the database.
 */

CConfigFile::CSectionIterator CConfigFile::SectionAdd
   (const std::string &strSectionName,
    const std::string &strComment)
{
   CSectionIterator iter;
   
   if (strSectionName.length () == 0 || strSectionName [0] != '/')
   {
      iter = CreateNewSection
                (std::string ("[/") + strSectionName + "]", strComment);
   }
   else
   {
      iter = CreateNewSection
                (std::string ("[") + strSectionName + "]", strComment);
   }
   m_fModified = true;
   return (iter);
} // CConfigFile::SectionAdd





/**
 * Remove an entire section and its sub-sections through a section name.
 */

void CConfigFile::SectionRemove
   (const std::string &strSectionName)
{
   CSectionIterator iterSect = SectionFind (strSectionName);
   if (iterSect == SectionEnd ())
   {
      // the given section does not exist --> ready
      return;
   }
   
   // remove the section and all of its sub-sections
   RemoveSection (iterSect.m_iterMap->second);
   
} // CConfigFile::SectionRemove





/**
 * Remove an entire section and its sub-sections through an iterator.
 */

void CConfigFile::SectionRemove
   (CSectionIterator &iterSect)
{
   // check if the iterator is not the end-iterator
   if (iterSect == SectionEnd ())
   {
      // there is nothing to remove starting at the end-iterator
      return;
   }
   
   // for security reasons we check if the section iterator contains valid
   // information
   if (iterSect.m_pMap == NULL)
   {
      // the iterator specified is uninitialised --> nothing to remove
      return;
   }
   
   // remove the section and all of its sub-sections
   RemoveSection (iterSect.m_iterMap->second);
   
} // CConfigFile::SectionRemove





/**
 * Check if the configuration database was modified after construction or
 * the last save operation.
 */

bool CConfigFile::IsModified (void) const
{
   return (m_fModified);
} // CConfigFile::IsModified





/**
 * Save the current database content to the current file.
 */

void CConfigFile::Save (void)
{
   // check if there is (still) a file for the given name
   struct stat statbuf;
   int         iRes;
   iRes = stat (m_strFileName.c_str (), &statbuf);
   if (iRes == 0 &&
       (statbuf.st_mode & (S_IFREG | S_IREAD | S_IWRITE)) ==
          (S_IFREG | S_IREAD | S_IWRITE))
   {
      // the file exists, rename the old file to a backup name
      iRes = rename (m_strFileName.c_str (),
                     CreateBackupFileName (m_strFileName).c_str ());
      if (iRes != 0)
      {
         std::ostringstream oss;
         oss << "Error renaming configuration file \"" << m_strFileName
             << "\" to \"" << CreateBackupFileName (m_strFileName)
             << "\" for backup: " << strerror (errno) << " (" << errno << ")";
         throw std::runtime_error (oss.str ());
      }
   }
   // either the file does not exist or another error occurred; defer the error
   // report until the attempt to create a new file

   // call the internal function that will try to overwrite an existing file
   // in any case
   SaveToFileInternal (m_strFileName);
   
   m_fModified = false;
   
} // CConfigFile::Save





/**
 * Save the current database content to a different file.
 */

void CConfigFile::SaveToFile
   (const std::string &strFileName,
    bool               fForceOverwrite)
{
   // check if there is already a file for the given name
   struct stat statbuf;
   int         iRes;
   iRes = stat (strFileName.c_str (), &statbuf);
   if (iRes == 0 &&
       (statbuf.st_mode & (S_IFREG | S_IREAD | S_IWRITE)) ==
          (S_IFREG | S_IREAD | S_IWRITE))
   {
      // if we may not overwrite it, throw an exception for error handling
      if (! fForceOverwrite)
      {
         throw std::runtime_error ("Configuration file already exists");
      }
      
      // rename the old file to a backup name
      iRes = rename (strFileName.c_str (),
                     CreateBackupFileName (strFileName).c_str ());
      if (iRes != 0)
      {
         std::ostringstream oss;
         oss << "Error renaming configuration file \"" << strFileName
             << "\" to \"" << CreateBackupFileName (strFileName)
             << "\" for backup: " << strerror (errno) << " (" << errno << ")";
         throw std::runtime_error (oss.str ());
      }
   }
   // either the file does not exist or another error occurred; defer the error
   // report until the attempt to create a new file
   
   // call the internal function that will try to overwrite an existing file
   // in any case
   SaveToFileInternal (strFileName);
   
   // if the write operation was successful, we now take the name specified
   m_strFileName = strFileName;
   
   m_fModified = false;
   
} // CConfigFile::SaveToFile





/**
 * Remove a section and all its sub-sections.
 */

void CConfigFile::RemoveSection
   (CSection *pSectToRemove)
{
   // check for valid parameter
   if (pSectToRemove == NULL)
   {
      throw std::runtime_error
               ("CConfigFile::RemoveSection: Invalid parameter");
   }
   
   // mark the configuration database as modified, even before any action takes
   // place
   m_fModified = true;
   
   // first erase all sub-sections for the given section
   CSection *pSect = pSectToRemove->m_pFirstSub;
   CSection *pParent;
   while (pSectToRemove->m_pFirstSub != NULL)
   {
      // descend to the next child without any further children
      if (pSect->m_pFirstSub != NULL)
      {
         pSect = pSect->m_pFirstSub;
         continue;
      }
      
      // if we are about to remove the top-level section or the one specified
      // as the function argument, something went wrong and we must abort
      if (pSect == m_pTopSect || pSect == pSectToRemove)
      {
         std::ostringstream oss;
         oss << "Error removing section \"" << pSectToRemove->GetFullName ()
             << "\", tree search left subtree to remove (internal error)";
         throw std::runtime_error (oss.str ());
         break;
      }
      
      // now we have a subnode without any children --> remove it and go back
      // to its parent
      pParent = pSect->m_pParent;
      RemoveSingleSection (pSect);
      pSect = pParent;
   }
   pSectToRemove->m_pFirstSub = NULL;
   pSectToRemove->m_pLastSub = NULL;
   
   // now finally remove the requested node that has no children (any more)
   RemoveSingleSection (pSectToRemove);

} // CConfigFile::RemoveSection





/**
 * Remove a single section without any children.
 */

void CConfigFile::RemoveSingleSection
   (CSection *pSectToRemove)
{
   // check for valid parameters
   assert (pSectToRemove != NULL);

   // erase the map entry for this section
   m_mapDb.erase (pSectToRemove->m_strFullName);
   
   // remove all key/value entries of this section
   CSection::Value_t *pKV;
   pSectToRemove->m_mapKV.clear ();
   while (pSectToRemove->m_pFirstValue != NULL)
   {
      pKV = pSectToRemove->m_pFirstValue;
      pSectToRemove->m_pFirstValue = pKV->m_pNext;
      delete pKV;
   }
   
   // now finally remove this section from the sub-section list of its parent
   // and delete it, but the root section always remains (it is the only one
   // without a parent)
   if (pSectToRemove->m_pParent != NULL)
   {
      if (pSectToRemove->m_pParent->m_pFirstSub == pSectToRemove)
      {
         pSectToRemove->m_pParent->m_pFirstSub = pSectToRemove->m_pNext;
      }
      if (pSectToRemove->m_pParent->m_pLastSub == pSectToRemove)
      {
         pSectToRemove->m_pParent->m_pLastSub = pSectToRemove->m_pPrev;
      }
      if (pSectToRemove->m_pNext != NULL)
      {
         pSectToRemove->m_pNext->m_pPrev = pSectToRemove->m_pPrev;
      }
      if (pSectToRemove->m_pPrev != NULL)
      {
         pSectToRemove->m_pPrev->m_pNext = pSectToRemove->m_pNext;
      }

      delete pSectToRemove;
   }

} // CConfigFile::RemoveSingleSection





/**
 * Read the database from a file.
 *
 * @pre The database is assumed to be empty.
 */

void CConfigFile::ReadFromFileInternal
   (const std::string &strFileName)
{
   // open the file for reading
   std::ifstream ifs (strFileName.c_str ());
   if (! ifs)
   {
      std::ostringstream oss;
      oss << "Error opening file \"" << strFileName
          << "\" for reading: " << strerror (errno) << " (" << errno << ")";
      throw std::runtime_error (oss.str ());
   }
   
   // if there is read error, let the stream generate an exception for
   // simplicity
   ifs.exceptions (std::ios_base::badbit);
   
   // prepare for reading the file, assume to start with a section header,
   // possibly preceeded by a section comment
   std::string  strComment = "";
   CSectionIterator iterSect = SectionEnd ();
   std::string      strLine;
   std::string      strKey;
   std::string      strValue;
   size_t           nCurrLine = 0;
   size_t           nFirstChar;
   
   // read the input file line by line
   while (! ifs.eof ())
   {
      // read the next line of input
      (void) getline (ifs, strLine);
      if (ifs.eof ())
      {
         break;
      }
      if (ifs.fail () || ifs.bad ())
      {
         std::ostringstream oss;
         oss << "Error reading from file after " << nCurrLine << " lines";
         throw std::runtime_error (oss.str ());
      }
      ++nCurrLine;
      
      // empty lines are simply ignored
      nFirstChar = strLine.find_first_not_of (" \t\r\n\f");
      if (strLine.length () == 0 || nFirstChar == std::string::npos)
      {
         continue;
      }
      
      // if the current line is a comment, just append it to the current
      // comment lines collection
      if (std::string ("#;").find (strLine [nFirstChar]) != std::string::npos)
      {
         // Note: The line separator is not part of the string read.
         strComment += strLine + '\n';
         continue;
      }
      
      // check if we shall start a new section
      if (strLine [nFirstChar] == '[')
      {
         try
         {
            iterSect =  CreateNewSection (strLine, strComment);
            strComment.clear ();
         }
         catch (std::exception &e)
         {
            std::ostringstream oss;
            oss << "Line " << nCurrLine << ": " << e.what ();
            throw std::runtime_error (oss.str ());
         }
         continue;
      }
      
      // so this must be a key/pair value line --> perform some checks and on
      // success enter the key/value pair to the current section
      
      // if no current section is set, the first non-comment and non-empty line
      // is no section --> error
      if (iterSect == SectionEnd ())
      {
         std::ostringstream oss;
         oss << "Line " << nCurrLine
             << ": Format error, file must start with a section";
         throw std::runtime_error (oss.str ());
      }
      
      // parse the key/value line and enter the key/value pair to the current
      // section
      try
      {
         ParseKeyValueLine (strLine, strKey, strValue);
         iterSect->AddKeyValuePair (strKey, strValue, strComment);
         strComment.clear ();
      }
      catch (std::exception &e)
      {
         std::ostringstream oss;
         oss << "Line " << nCurrLine << ": " << e.what ();
         throw std::runtime_error (oss.str ());
      }
   }
   
} // CConfigFile::ReadFromFileInternal





/**
 * Save the database to a file.
 */
void CConfigFile::SaveToFileInternal
   (const std::string &strFileName)
{
   std::ofstream ofs (strFileName.c_str (),
                      std::ios_base::out | std::ios_base::trunc);
   if (! ofs)
   {
      std::ostringstream oss;
      oss << "Error opening file \"" << strFileName
          << "\" for writing: " << strerror (errno) << " (" << errno << ")";
      throw std::runtime_error (oss.str ());
   }
   
   // if writing to the stream fails, let the stream create an exception for
   // simplicity
   ofs.exceptions (std::ios_base::badbit | std::ios_base::failbit);
   
   // walk through all sections of the configuration database, recursively
   bool fFirstSection = true;
   SaveSection (ofs, m_pTopSect, fFirstSection);
   
} // CConfigFile::SaveToFileInternal





/**
 * Save a complete section to a stream.
 */

void CConfigFile::SaveSection
   (std::ofstream  &ofs,
    const CSection *pSect,
    bool           &fFirstSection)
{
   std::string        str;
   CSection::Value_t *pKV;
   CSection          *pSubSect;
   
   // check for valid parameters
   if (pSect == NULL)
   {
      throw std::runtime_error ("CConfigFile::SaveSection: Invalid parameter");
   }
   
   // write the section heading including the section comment, but only if this
   // is not the root section (the root section cannot contain any key/value
   // pairs)
   if (pSect->GetShortName ().length () != 0)
   {
      // print some leading empty lines before every section (and its comment)
      if (! fFirstSection)
      {
         ofs << std::endl << std::endl;
      }
      fFirstSection = false;
      
      // print out the comment for the current section
      if (pSect->GetComment ().length () != 0)
      {
         // Note: Comment includes newline at its end.
         ofs << pSect->GetComment ();
      }
      
      // print out the section heading, but without a leading "/"
      str = pSect->GetFullName ();
      if (str.length () > 0 && str [0] == '/')
      {
         str = str.substr (1);
      }
      ofs << '[' << str << ']' << std::endl;
      
      // walk through all entries of the section
      for (pKV = pSect->m_pFirstValue; pKV != NULL; pKV = pKV->m_pNext)
      {
         // print out an empty line before the first entry (and its comment)
         if (pKV == pSect->m_pFirstValue)
         {
            ofs << std::endl;
         }
         
         // print out the comment for the current key/value entry
         if (pKV->m_strComment.length () != 0)
         {
            // Note: Comment includes newline at its end.
            ofs << pKV->m_strComment;
         }
         
         // print out the key/value line
         ofs << pKV->m_strKey << " = " << pKV->m_strValue << std::endl;
      }
      
      // after each section enter an empty line
      ofs << std::endl;
   }
   
   // finally save all sub-sections
   for (pSubSect = pSect->m_pFirstSub;
        pSubSect != NULL;
        pSubSect = pSubSect->m_pNext)
   {
      SaveSection (ofs, pSubSect, fFirstSection);
   }
   
} // CConfigFile::SaveSection





/**
 * Create a backup file name for a specified one.
 */

std::string CConfigFile::CreateBackupFileName
   (const std::string &strOrgFileName)
{
   return (strOrgFileName + "~");
} // CConfigFile::CreateBackupFileName





/**
 * Create a new section from a configuration line.
 */

CConfigFile::CSectionIterator CConfigFile::CreateNewSection
   (const std::string &strLine,
    const std::string &strComment)
{
   // extract the sub-string for the real section name
   std::string strRawSect;
   size_t      nStart;
   size_t      nEnd;
   nStart = strLine.find ('[');
   nEnd = strLine.find (']');
   if (nStart == std::string::npos || nEnd == std::string::npos ||
       nStart >= nEnd)
   {
      throw std::runtime_error ("Invalid section line format (delimiters)");
   }
   strRawSect = strLine.substr (nStart + 1, nEnd - nStart - 1);
   nStart = strRawSect.find_first_not_of (" \t\r\n\f");
   nEnd = strRawSect.find_last_not_of (" \t\r\n\f");
   if (nStart == std::string::npos || nEnd == std::string::npos)
   {
      throw std::runtime_error ("Invalid section name, must be non-empty");
   }
   strRawSect = strRawSect.substr (nStart, nEnd - nStart + 1);
   
   // walk through the components of the section name and ensure the existance
   // of all parent sections of the new section to create
   std::string            strFullName = "/";
   std::string            strCurrShortName;
   size_t                 nCurrPos = 0;
   CSection              *pSectParent = m_pTopSect;
   CSectionIterator       iterSect = SectionEnd ();
   CSectionMap::iterator  iterMap;
   assert (pSectParent != NULL);
   while (nCurrPos < strRawSect.length ())
   {
      // find the start and the end of the path component from the current
      // position
      nStart = strRawSect.find_first_not_of ("/", nCurrPos);
      if (nStart == std::string::npos)
      {
         // no more path component found --> ready or error
         break;
      }
      nEnd = strRawSect.find_first_of ("/", nStart);
      if (nEnd == std::string::npos)
      {
         nEnd = strRawSect.length ();
      }
      
      // create the new section name
      strCurrShortName = strRawSect.substr (nStart, nEnd - nStart);
      strFullName += strCurrShortName;
      
      // check for existence of the current section
      iterMap = m_mapDb.find (strFullName);
      if (iterMap == m_mapDb.end ())
      {
         // section does not exist --> create it, but without comment (may not
         // belong to _this_ section, but the last to be created instead)
         CSection                               *pNewSect;
         std::pair<CSectionMap::iterator, bool>  resPair;

         pNewSect = new CSection (pSectParent, m_fModified,
                                  strFullName, strCurrShortName, "");
         pNewSect->m_pPrev = pSectParent->m_pLastSub;
         pNewSect->m_pNext = NULL;
         if (pSectParent->m_pFirstSub == NULL)
         {
            pSectParent->m_pFirstSub = pNewSect;
         }
         if (pSectParent->m_pLastSub != NULL)
         {
            pSectParent->m_pLastSub->m_pNext = pNewSect;
         }
         pSectParent->m_pLastSub = pNewSect;

         resPair = m_mapDb.insert
                      (CSectionMap::value_type (strFullName, pNewSect));
         iterSect = CSectionIterator ("", &m_mapDb, resPair.first);
         
         pSectParent = pNewSect;
      }
      else
      {
         iterSect = CSectionIterator ("", &m_mapDb, iterMap);
         
         pSectParent = iterMap->second;
      }
      
      // advance the current position behind the end of the last path component
      strFullName += '/';
      nCurrPos = nEnd;
   }
   
   // if no section was created or found, there must be some mistake
   if (iterSect == SectionEnd ())
   {
      std::ostringstream oss;
      oss << "Unable to create new section \"" << strRawSect
          << "\" or to find it as an existing one";
      throw std::runtime_error (oss.str ());
   }
   
   // assign the comment to the last section created
   iterSect->SetComment (strComment);
   
   // return the address of the last section created
   return (iterSect);
} // CConfigFile::CreateNewSection





/**
 * Parse a key/value configuration line.
 */

void CConfigFile::ParseKeyValueLine
   (const std::string &strLine,
    std::string       &strKey,
    std::string       &strValue)
{
   size_t nSep;
   size_t nFirst;
   size_t nLast;
   
   // find the "=" separating the key from its value
   nSep = strLine.find ('=');
   if (nSep == std::string::npos)
   {
      throw std::runtime_error
               ("Assignment character \"=\" not found in Key/value line");
   }
   
   // separate the line into the two parts key and value
   strKey = strLine.substr (0, nSep);
   if (nSep + 1 < strLine.length ())
   {
      strValue = strLine.substr (nSep + 1);
   }
   else
   {
      strValue = "";
   }
   
   // remove leading and following whitespace from the key (first part), must
   // be non-empty after this operation
   nFirst = strKey.find_first_not_of (" \t\r\n\f");
   nLast = strKey.find_last_not_of (" \t\r\n\f");
   if (nFirst != std::string::npos && nLast != std::string::npos)
   {
      strKey = strKey.substr (nFirst, nLast + 1 - nFirst);
   }
   else
   {
      strKey.clear ();
   }
   if (strKey.length () == 0)
   {
      throw std::runtime_error ("Empty key string in key/value line");
   }
   
   // remove leading and following whitespace from the value (second part)
   nFirst = strValue.find_first_not_of (" \t\r\n\f");
   nLast = strValue.find_last_not_of (" \t\r\n\f");
   if (nFirst != std::string::npos && nLast != std::string::npos)
   {
      strValue = strValue.substr (nFirst, nLast + 1 - nFirst);
   }
   else
   {
      strValue.clear ();
   }
   
} // CConfigFile::ParseKeyValueLine





// === Implementation of public functions ================================





// === Implementation of private functions ===============================
