/**
* \brief INI File handling library
* \file ezini.c
* \author Michael Dipperstein (mdipperstein@gmail.com)
* \date November 22, 2015
*
* This file implements a set of library functions that maybe be used
* to create, update, and/or parse INI files.
*
* \copyright Copyright (C) 2015, 2019 by Michael Dipperstein
* (mdipperstein@gmail.com)
*
* \par
* This file is part of the ezini library.
*
* \license
* The ezini library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* \par
* The ezini library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* \par
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
*/
/**
* \defgroup library Library Code
* \brief This module contains the code for the ezini INI file handling
* library
* @{
*/
/***************************************************************************
* INCLUDED FILES
***************************************************************************/
#include
#include
#include
#include
#include
#include "ezini.h"
/***************************************************************************
* TYPE DEFINITIONS
***************************************************************************/
/**
* \struct ini_key_list_t
* \brief A structure used for creating linked lists of key/value pairs
* for a each section.
*/
/**
* \typedef struct ini_key_list_t
* \brief A shortcut for struct ini_key_list_t
*/
typedef struct ini_key_list_t
{
char *key; /*!< pointer to a a NULL terminated string
containing key name for this entry */
char *value; /*!< pointer to a NULL terminated string
containing key value for this entry Use
ASCII strings to represent numbers */
struct ini_key_list_t *next;/*!< pointer to the next key/value pair in
in this section */
} ini_key_list_t;
/**
* \struct ini_section_list_t
* \brief A structure used for creating linked lists of sections, each
* maintaining its own list of key/value pairs.
*/
/**
* \typedef struct ini_section_list_t
* \brief A shortcut for struct ini_section_list_t
*/
typedef struct ini_section_list_t
{
char *section; /*!< pointer to a NULL terminated string
containing the section name */
ini_key_list_t *members; /*!< pointer to the list of all key/value
pairs in this section */
struct ini_section_list_t *next; /*!< pointer to the next section in
the list of entries */
} ini_section_list_t;
/***************************************************************************
* PROTOTYPES
***************************************************************************/
/* allocate */
static ini_key_list_t *NewKeyList(const char *key, const char *value);
static ini_section_list_t *NewSectionList(const char *section, const char *key,
const char *value);
/* free */
static void FreeKeyList(ini_key_list_t *list);
static void FreeEntry(ini_entry_t *entry);
/* utilities */
static char *SkipWS(const char *str);
static char *DupStr(const char *src);
static char *GetLine(FILE *fp);
/***************************************************************************
* FUNCTIONS
***************************************************************************/
/**
* \fn int AddEntryToList(ini_entry_list_t *list, const char *section,
* const char *key, const char *value)
*
* \brief This function adds a (section, key, value) entry to an entry list.
*
* \param list A pointer to an ini_entry_list_t that points to the
* head of entry list being modified. Pass a pointer to an ini_entry_list_t
* pointing to NULL if the list needs to be created.
*
* \param section A NULL terminated string containing the name of the
* section for the entry being added.
*
* \param key A NULL terminated string containing the name of the key for
* the entry being added.
*
* \param value A NULL terminated string containing the value of the key for
* the entry being added. All values must be represented as strings. They may
* be converted to/from strings by the calling program.
*
* \effects
* Information used to generate an entry structure containing copies of
* the (section, key, value) entry is added to the list passed as a parameter.
* Memory will be dynamically allocated as needed.
*
* \returns 0 for success, Non-zero on error. Error type is contained in
* errno.
*
* This function adds information used to create a (section, key, value) entry
* to an entry list.
*
* If an entry containing the same section name and key already exists,
* the new value will overwrite the old value.
*
* If the entry is for an existing section, it will be added to the end of the
* list for that section.
*
* If the entry is for a new section, a new section will be added to the list of
* sections, and the key/value pair will be the first entry of the section.
*/
int AddEntryToList(ini_entry_list_t *list, const char *section,
const char *key, const char *value)
{
ini_section_list_t *next;
int result;
/* handle empty list */
if (NULL == *list)
{
/* add the first entry to the list */
*list = NewSectionList(section, key, value);
if (NULL == *list)
{
return -1;
}
return 0;
}
next = *list;
result = 1;
while (1)
{
result = strcmp(section, next->section);
if (0 == result)
{
break; /* match, insert here */
}
if (NULL == next->next)
{
break; /* no match, create new section here */
}
next = next->next;
}
if (0 == result)
{
ini_key_list_t *member;
member = next->members;
while (1)
{
result = strcmp(key, member->key);
if (0 == result)
{
break; /* match, insert here */
}
if (NULL == member->next)
{
break; /* no match, create new section here */
}
member = member->next;
}
if (0 == result)
{
/* key exists, change value */
free(member->value);
member->value = DupStr(value);
if (NULL == member->value)
{
return -1;
}
}
else
{
/* new key, add to list */
member->next = NewKeyList(key, value);
if (NULL == member->next)
{
return -1;
}
}
}
else
{
/* add the section to the list with this key and value */
next->next = NewSectionList(section, key, value);
}
return 0;
}
/**
* \fn void FreeList(ini_entry_list_t list)
*
* \brief This function frees all of the members of an entry list.
*
* \param list A pointer to the head of an ini_entry_list_t
*
* \effects
* All of the memory allocated for all of the entries in an entry
* list will be freed.
*
* \returns Nothing
*
* This function uses recursion to step to the tail of the list and deletes
* section entries on the way back up.
*/
void FreeList(ini_entry_list_t list)
{
/* recurse to the end of the list and free everything on the way back */
if (list->next != NULL)
{
FreeList(list->next);
}
if (list->section != NULL)
{
/* free the section name */
free(list->section);
}
if (list->members != NULL)
{
FreeKeyList(list->members);
}
free(list);
}
/**
* \fn int MakeINIFile(const char *iniFile, const ini_entry_list_t list)
*
* \brief This function creates the specified INI file from the list of
* entries passed as an argument.
*
* \param iniFile The name of the INI file to be created. stdout will be
* used if iniFile is NULL.
*
* \param list A pointer to a list of that will be used to construct
* (section, key, value) entries.
*
* \effects
* The specified file is created and the (section, key, value)
* triples generated from the entry list are written to the file. If the
* specified file already exists, it will be overwritten.
*
* \returns 0 for success, Non-zero on error. Error type is contained in
* errno.
*
* This function creates the specified INI file from the list of entries
* passed as an argument. Any existing INI file with the same name in the
* same path will be overwritten.
*/
int MakeINIFile(const char *iniFile, const ini_entry_list_t list)
{
ini_entry_list_t section;
ini_key_list_t *members;
FILE *fp;
if (NULL == list)
{
errno = EINVAL;
return -1;
}
if (NULL == iniFile)
{
fp = stdout;
}
else
{
fp = fopen(iniFile, "w");
if (NULL == fp)
{
return -1;
}
}
section = list;
while (section != NULL)
{
fprintf(fp, "[%s]\n", section->section);
members = section->members;
while (members != NULL)
{
fprintf(fp, "%s = %s\n", members->key, members->value);
members = members->next;
}
fprintf(fp, "\n");
section = section->next;
}
if (fp != stdout)
{
fclose(fp);
}
return 0;
}
/**
* \fn int AddEntryToFile(const char *iniFile, const ini_entry_list_t list)
*
* \brief This function adds (section, key, value) entries in an entry list
* to an INI file.
*
* \param iniFile The name of the INI file to be modified.
*
* \param list A pointer to a list of entries to be added to the INI file.
*
* \effects
* The INI file will be re-written containing the results of adding
* the entries in the entry list to the entries already contained in the INI
* file.
*
* \returns 0 for success, Non-zero on error. Error type is contained in
* errno.
*
* This function adds (section, key, value) entries in an entry list to an
* INI file. Section order will be maintained with new sections added to the
* end of the INI file. If an entry containing the same section name and key
* already exists, the new value will overwrite the old value.
*/
int AddEntryToFile(const char *iniFile, const ini_entry_list_t list)
{
ini_entry_t entry;
ini_entry_list_t merged;
ini_entry_list_t here;
int result;
FILE *fp;
if (NULL == iniFile)
{
errno = EINVAL;
return -1;
}
if (NULL == list)
{
errno = EINVAL;
return -1;
}
fp = fopen(iniFile, "r");
if (NULL == fp)
{
return -1;
}
merged = NULL;
entry.section = NULL;
entry.key = NULL;
entry.value = NULL;
/* read ini file back into an entry list */
while ((result = GetEntryFromFile(fp, &entry)) > 0)
{
AddEntryToList(&merged, entry.section, entry.key, entry.value);
}
fclose(fp);
if (result < 0)
{
FreeList(merged);
return -1;
}
/* add entries passed into this function to entries from INI file */
here = list;
while (here != NULL)
{
ini_key_list_t *members;
members = here->members;
while (members != NULL)
{
AddEntryToList(&merged, here->section, members->key,
members->value);
members = members->next;
}
here = here->next;
}
/* re-write INI file from merged entry list */
result = MakeINIFile(iniFile, merged);
FreeList(merged);
return result;
}
/**
* \fn int DeleteEntryFromFile(const char *iniFile, const char *section,
* const char *key)
*
* \brief This function deletes all entries from an INI file that match the
* section and key passed as an argument.
*
* \param iniFile The name of the INI file containing the entry to be
* deleted.
*
* \param section A pointer to a NULL terminated string containing the name
* of the section of the entry to be deleted.
*
* \param key A pointer to a NULL terminated string containing the name of
* the key of the entry to be deleted.
*
* \effects
* The INI file will be re-written without any entries that match
* the section and key to be deleted. Empty sections will not be deleted.
*
* \returns 0 for success, Non-zero on error. Error type is contained in
* errno.
*
* This function deletes all entries from an INI file that match the section
* and key passed as an argument. Empty sections will not be deleted.
*
* \note There will never be more than one matching entry in INI files
* created by this library.
*/
int DeleteEntryFromFile(const char *iniFile, const char *section,
const char *key)
{
ini_entry_t entry;
ini_entry_list_t list;
int result;
FILE *fp;
if (NULL == iniFile)
{
errno = EINVAL;
return -1;
}
if (NULL == section)
{
errno = EINVAL;
return -1;
}
if (NULL == key)
{
errno = EINVAL;
return -1;
}
fp = fopen(iniFile, "r");
if (NULL == fp)
{
return -1;
}
list = NULL;
entry.section = NULL;
entry.key = NULL;
entry.value = NULL;
/* read ini file back into a structure */
while ((result = GetEntryFromFile(fp, &entry)) > 0)
{
if (0 != strcmp(entry.section, section))
{
/* this isn't one we're supposed to delete */
AddEntryToList(&list, entry.section, entry.key, entry.value);
}
else if (0 != strcmp(entry.key, key))
{
/* this isn't one we're supposed to delete */
AddEntryToList(&list, entry.section, entry.key, entry.value);
}
}
fclose(fp);
if (result < 0)
{
FreeList(list);
return -1;
}
result = MakeINIFile(iniFile, list);
FreeList(list);
return result;
}
/**
* \fn int GetEntryFromFile(FILE *iniFile, ini_entry_t *entry)
*
* \brief This function parses an INI file stream passed as an input,
* searching for the next (section, key, value) triple.
*
* \param iniFile A pointer to the INI file to be parsed. It must be
* opened for reading.
*
* \param entry A pointer to the entry structure used to store the discovered
* (section, key, value) triple.
*
* \effects
* The specified file is read until it discovers an entry.
*
* \returns 1 when an entry is found\n
* 0 when no more entries can be found\n
* -1 for an error. Error type is contained in errno.
*
* This function parses an INI file stream passed as an input, searching for
* the next (section, key, value) triple. The resulting triple will be used
* to populate the entry structure passed as a parameter.
*/
int GetEntryFromFile(FILE *iniFile, ini_entry_t *entry)
{
char *line;
char *ptr;
if (NULL == iniFile)
{
errno = EINVAL;
return -1;
}
if (NULL == entry)
{
errno = EINVAL;
return -1;
}
/* handle section names, comments, and blank lines */
while ((line = GetLine(iniFile)) != NULL)
{
/* skip leading spaces and blank lines */
ptr = SkipWS(line);
/* skip blank lines and lines starting with ';' or '#' */
if (*ptr == '\0' || *ptr == ';' || *ptr == '#')
{
free(line);
continue;
}
else if (*ptr == '[')
{
/* possible new section */
char *end;
end = strchr(ptr, ']');
if (NULL == end)
{
FreeEntry(entry);
errno = EILSEQ;
return -1;
}
/* we have the full string for a new section, trim white space */
ptr = SkipWS(ptr + 1);
while (isspace(*(end - 1)))
{
end--;
}
*end = '\0';
free(entry->section);
entry->section = DupStr(ptr);
free(line);
}
else
{
/* this line should be key = value */
break;
}
}
/* we either have a non-section line or nothing left to get */
if (NULL == line)
{
/* nothing left to get */
FreeEntry(entry);
return 0;
}
/* the only other allowable lines are of the form key = value */
free(entry->key); /* free old key */
entry->key = ptr;
ptr++;
while (*ptr != '=')
{
if (*ptr == '\0')
{
/* didn't find '=' */
entry->key = NULL;
FreeEntry(entry);
free(line);
errno = EILSEQ;
return -1;
}
ptr++;
}
/* we found the '=' separating key and value trim white space */
free(entry->value); /* free old value */
entry->value = ptr + 1;
ptr--;
while (isspace(*ptr))
{
ptr--;
}
*(ptr + 1) = '\0';
entry->key = DupStr(entry->key);
ptr = entry->value;
/* now skip white space after '=' */
while (*ptr == ' ' || *ptr =='\t')
{
if (*ptr == '\0')
{
FreeEntry(entry);
free(line);
errno = EILSEQ;
return -1;
}
ptr++;
}
/* we found the start of value, trim trailing white space */
entry->value = ptr;
ptr = entry->value + strlen(entry->value) - 1;
while (isspace(*ptr))
{
ptr--;
}
*(ptr + 1) = '\0';
entry->value = DupStr(entry->value);
free(line);
return 1;
}
/**
* \fn ini_key_list_t *NewKeyList(const char *key, const char *value)
*
* \brief This function allocates memory for a new ini_key_list_t type
* variable and populates it with the data passed as parameters
*
* \param key A pointer to a NULL terminated string containing the key name
*
* \param key A pointer to a NULL terminated string containing the value of
* this key. Use ASCII strings to represent numbers.
*
* \effects
* Memory will be allocated for a new ini_key_list_t and copies
* of the key and value strings passed as a parameter. key and value are
* copied into the appropriate fields and the next pointer is set to NULL.
*
* \returns A pointer to the ini_key_list_t item that was allocated. The
* pointer will be NULL if an error occurs.
*
* This function allocates memory for a new ini_key_list_t and copies
* of the key and value strings passed as a parameter. The next pointer
* will be set to NULL.
*/
static ini_key_list_t *NewKeyList(const char *key, const char *value)
{
ini_key_list_t *item;
item = (ini_key_list_t *)malloc(sizeof(ini_key_list_t));
if (NULL == item)
{
return NULL;
}
/* allocation succeeded copy key and value */
item->next = NULL;
item->key = DupStr(key);
if (NULL == item->key)
{
free(item);
return NULL;
}
item->value = DupStr(value);
if (NULL == item->value)
{
free(item->key);
free(item);
return NULL;
}
return item;
}
/**
* \fn ini_section_list_t *NewSectionList(const char *section, const char *key,
* const char *value)
*
* \brief This function allocates memory for a new ini_section_list_t type
* variable and populates it with the data passed as parameters
*
* \param section A pointer to a NULL terminated string containing the section
* name
* \param key A pointer to a NULL terminated string containing the key name
*
* \param key A pointer to a NULL terminated string containing the value of
* this key. Use ASCII strings to represent numbers.
*
* \effects
* Memory will be allocated for a new ini_section_list_t and copies
* of the section, key, value strings passed as a parameter. section is
* copied to the appropriate field and a new key list items is created fo
* the key and value strings. The next pointer will be set to NULL.
*
* \returns A pointer to the ini_section_list_t item that was allocated. The
* pointer will be NULL if an error occurs.
*
* This function allocates memory for a new ini_section_list_t and copies
* of the section, key, value strings passed as a parameter. A ini_key_list_t
* is allocated for the key and value strings. The next pointer is set to NULL.
*/
static ini_section_list_t *NewSectionList(const char *section, const char *key,
const char *value)
{
ini_section_list_t *item;
item = (ini_section_list_t *)malloc(sizeof(ini_section_list_t));
if (NULL == item)
{
return NULL;
}
/* now populate item */
item->next = NULL;
item->section = DupStr(section);
if (NULL == item->section)
{
free(item);
return NULL;
}
/* start a member list with the current key and value */
item->members = NewKeyList(key, value);
if (NULL == item->members)
{
free(item->section);
free(item);
return NULL;
}
return item;
}
/**
* \fn void FreeKeyList(ini_key_list_t *list)
*
* \brief This function frees all of the members of a key list.
*
* \param list A pointer to the head of an ini_key_list_t
*
* \effects All of the memory allocated for all of the key/value pairs in
* a key list will be freed.
*
* \returns Nothing
*
* This function uses recursion to step to the tail of the list and deletes
* key/value entries on the way back up.
*/
static void FreeKeyList(ini_key_list_t *list)
{
/* recurse to the end of the list and free everything on the way back */
if (list->next != NULL)
{
FreeKeyList(list->next);
}
free(list->key);
free(list->value);
free(list);
}
/**
* \fn static void FreeEntry(ini_entry_t *entry)
*
* \brief This function frees the allocated memory that is pointed to by the
* members of an ini_entry_t structure.
*
* \param entry A pointer to the entry structure containing the members
* pointing to the memory to be freed.
*
* \effects Dynamically allocated memory is freed and entry data is set
* to NULL.
*
* \returns Nothing
*/
static void FreeEntry(ini_entry_t *entry)
{
free(entry->section);
free(entry->key);
free(entry->value);
entry->section = NULL;
entry->key = NULL;
entry->value = NULL;
}
/**
* \fn static char *SkipWS(const char *str)
*
* \brief This function returns a pointer to the first non-space in the
* string passed as a parameter.
*
* \param str A pointer to the string being searched.
*
* \effects None
*
* \returns A pointer to the first non-space in str.
*/
static char *SkipWS(const char *str)
{
char *c;
c = (char *)str;
while(isspace(*c))
{
c++;
}
return c;
}
/**
* \fn static char *DupStr(const char *src)
*
* \brief This function returns a copy of the string passed as a parameter.
*
* \param str A pointer to the string being being copied.
*
* \effects Memory is dynamically allocated to hold a duplicate of the
* input string.
*
* \returns A copy of str in malloced memory is returned on success. NULL
* is returned on failure.
*
* This function returns a copy of the string passed as a parameter. The
* memory for the copy is allocated by malloc() and must be freed by the caller.
*/
static char *DupStr(const char *src)
{
char *dest;
if (NULL == src)
{
return NULL;
}
dest = (char *)malloc(strlen(src) + 1);
if (NULL != dest)
{
strcpy(dest, src);
}
return dest;
}
/**
* \fn static char *GetLine(FILE *fp)
*
* \brief This function returns a NULL terminated array of char containing the
* next line in the file passed as an argument.
*
* \param fp A pointer to the file being read.
*
* \effects One line is read from fp and copied into a dynamically allocated
* string.
*
* \returns A NULL terminated array of char containing the next line in fp
* is retured. The array must be free by the caller. NULL is returned at end
* of file.
*
* This function returns a NULL terminated array of char containing the next
* line in the file passed as an argument. The memory for the string returned
* is allocated by malloc() and must be freed by the caller.
*/
static char *GetLine(FILE *fp)
{
char *line; /* string to read line into */
char *next; /* where to write the next characters into */
const size_t chunkSize = 32;
size_t lineSize;
if ((NULL == fp) || feof(fp))
{
return NULL;
}
lineSize = chunkSize;
line = (char *)malloc(lineSize * sizeof(char));
if (NULL == line)
{
/* allocation failed */
return NULL;
}
line[0] = '\0';
next = line;
while (NULL != fgets(next, lineSize - strlen(line), fp))
{
if ('\n' == line[strlen(line) - 1])
{
/* we got to the EOL strip off the trailing '\n' and exit */
line[strlen(line) - 1] = '\0';
break;
}
else
{
/* there's still more on this line */
lineSize += chunkSize;
line = (char *)realloc(line, lineSize);
if (NULL == line)
{
return NULL;
}
next = line + strlen(line);
}
}
return line;
}
/**@}*/