/* inifile.c:
*
* vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
****************************************************************
* Copyright (C) 2005 Canonical Limited
* Authors: Robert Collins <robert.collins@canonical.com>
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/str.h"
#include "hackerlab/mem/alloc-limits.h"
#include "hackerlab/rx-posix/regex.h"
#include "hackerlab/vu/safe.h"
#include "libfsutils/file-contents.h"
#include "libinifile/inifile.h"
static t_uchar const *inifile_section_string = "^[[:space:]]*\\[([[:alnum:][:space:])]*)\\]";
static t_uchar const *inifile_non_comment = "^[[:space:]]*(;|#).*";
static t_uchar const *key_pattern_string = "^[[:space:]]*([[:alnum:]_)]+)[[:space:]]*=([^#;]*)(;(.*))?$";
/**
* \brief load an inifile from disk
*
* initialises the inifile with the content of the file from disk.
* \param inifile the inifile to be loaded into
* \param filename the file to be loaded
* \return 0 on success
*/
int
inifile_load (inifile_t *inifile, t_uchar const *filename)
{
/* FIXME: check for file existence 20050308 RBC*/
t_uchar *content = file_contents (filename);
inifile_process_text (inifile, content);
lim_free (0, content);
return 0;
}
/**
* \brief save a inifile merge to disk
* \param inifile the inifile to save
* \param merge_index the merge to save
* \param filename the file to save
*/
int
inifile_save_merge (inifile_t *inifile, int merge_index, t_uchar const *filename)
{
/* FIXME use non 'safe' calls and possibly fail
* 2000308 RBC */
t_uchar *content = inifile_get_text_merge (inifile, merge_index);
file_set_contents (filename, content);
lim_free (0, content);
return 0;
}
/**
* \brief initialise an inifile
*
* \param inifile the inifile to be loaded into
* \return 0 on success
*/
void
inifile_init (inifile_t *inifile)
{
inifile->content = NULL;
}
/**
* \brief process the text in an inifile
*
* \param inifile the inifile to apply results to
* \param content the text to process.
* \return 0 on success
*/
int
inifile_process_text (inifile_t *inifile, t_uchar const *content)
{
rel_table temp;
temp = rel_nl_split (content);
rel_add_records (&inifile->content, rel_make_record ("[]", 0), 0);
rel_append_x (&inifile->content, temp);
rel_free_table (temp);
return 0;
}
/**
* \brief get the text representation of an inifile
*
* \param inifile the inifile to retrieve
* \param merge_index get the nth inifile from a merged inifile.
* \return a heap allocated string
*/
t_uchar *
inifile_get_text_merge (inifile_t *inifile, int merge_index)
{
int line;
t_uchar *result=NULL;
int copying = 0;
int current_merge = 0;
rel_for_each(inifile->content, line)
{
if (inifile_line_type (inifile->content[line][0]) == INIFILE_SECTION && !str_cmp("[]", inifile->content[line][0]))
copying = current_merge++ == merge_index;
else if (copying)
result = str_replace (result, str_alloc_cat_many (0, result, inifile->content[line][0], "\n", str_end));
}
return result;
}
/**
* \brief get the text representation of an inifile
*
* \param inifile the inifile to retrieve
* \return a heap allocated string
*/
t_uchar *
inifile_get_text (inifile_t *inifile)
{
return inifile_get_text_merge (inifile, 0);
}
/**
* \brief free the resources of inifile
*/
void
inifile_finalise (inifile_t *inifile)
{
rel_free_table (inifile->content);
inifile->content = NULL;
}
/**
* \brief helper function for getting a match from a regex pattern
*
* \param pattern a cached regex pattern
* \param pattern_string the regex string to compile if needed
* \param input the string to match against
* \param match the nth match to return
*/
static t_uchar *
inifile_regex_get_match (regex_t **pattern, t_uchar const *pattern_string, t_uchar const *input, unsigned int match)
{
regmatch_t matches[match];
int regexres;
if (!*pattern)
{
int re_error;
*pattern = lim_malloc (0, sizeof (**pattern));
re_error = regcomp (*pattern, pattern_string, REG_EXTENDED);
invariant (!re_error);
}
if ((regexres = regexec (*pattern, input, match, matches, 0)))
{
char buf[50];
if (regexres == REG_NOMATCH)
return NULL;
regerror (regexres, *pattern, buf, 50);
safe_printfmt (2, "failed during regex match for section name: %d %s\n", regexres, buf);
return NULL;
}
return str_save_n (0, input + matches[match - 1].rm_so, matches[match - 1].rm_eo - matches[match - 1].rm_so);
}
/**
* \brief get the section name from a line
* \param line the line
* \return t_char * allocated on the heap, or NULL if not a section line.
*/
t_uchar *
inifile_section_name (t_uchar const *line)
{
static regex_t * pattern = NULL;
return inifile_regex_get_match (&pattern, inifile_section_string, line, 2);
}
/* the standard key patten */
static regex_t * key_pattern = NULL;
/* group 2 is the keyname, */
/**
* \brief get the key name from a line
*
* note that comment lines have a key of ""
* \param line the line
* \return t_char * allocated on the heap, or NULL if not a key line.
*/
t_uchar *
inifile_key_name (t_uchar const *line)
{
static regex_t * comment = NULL;
t_uchar *result;
result = inifile_regex_get_match (&key_pattern, key_pattern_string, line, 2);
if (!result)
{
result = inifile_regex_get_match (&comment, inifile_non_comment, line, 2);
if (result)
result = str_replace (result, str_save (0, ""));
}
return result;
}
/**
* \brief get the value from a line
*
* note that comment lines have no value
* \param line the line
* \return t_char * allocated on the heap.
*/
t_uchar *
inifile_value (t_uchar const *line)
{
static regex_t * comment = NULL;
t_uchar *result;
result = inifile_regex_get_match (&key_pattern, key_pattern_string, line, 3);
if (!result)
{
result = inifile_regex_get_match (&comment, inifile_non_comment, line, 2);
if (result)
result = str_replace (result, str_save (0, ""));
}
return result;
}
/**
* \brief get the comment from a line
*
* \param line the line
* \return t_char * allocated on the heap.
*/
t_uchar *
inifile_comment (t_uchar const *line)
{
static regex_t * comment = NULL;
t_uchar *result;
result = inifile_regex_get_match (&key_pattern, key_pattern_string, line, 5);
if (!result)
{
result = inifile_regex_get_match (&comment, inifile_non_comment, line, 2);
if (result)
result = str_replace (result, str_save (0, line));
}
return result;
}
/**
* \brief get the list of keys a particular section has
*
* keys called "" are comment lines
* \param inifile the inifile to query
* \param section the section to return. "" is the top of the file
* \return a rel_table containing the sections' key list.
*/
rel_table
inifile_get_section (inifile_t *inifile, t_uchar const *section)
{
rel_table result = NULL;
int line;
t_uchar * current_section = str_save (0, "");
rel_for_each (inifile->content, line)
{
if (inifile_line_type (inifile->content[line][0]) == INIFILE_SECTION)
{
current_section = str_replace (current_section, inifile_section_name (inifile->content[line][0]));
}
else if (!str_casecmp(current_section, section))
{
int index = 0;
t_uchar *temp_key=inifile_key_name (inifile->content[line][0]);
if (!temp_key)
continue;
while (index < rel_n_records (result) && str_casecmp (temp_key, result[index][0]))
++index;
if (index == rel_n_records (result))
rel_add_records (&result, rel_make_record (temp_key, 0), 0);
lim_free (0, temp_key);
}
}
lim_free (0, current_section);
return result;
}
/**
* \brief get the list of values (and comments) for a keyname in a particular section
*
* to get just comments, use "" as the key
* \param inifile the inifile to query
* \param section the section to return. "" is the top of the file
* \param key the key to query.
* \return a rel_table containing the keys values in field 0 and comment in field 1.
*/
rel_table
inifile_get_key_values (inifile_t *inifile, t_uchar const *section, t_uchar const *key)
{
rel_table result = NULL;
int line;
t_uchar * current_section = str_save (0, "");
rel_for_each (inifile->content, line)
{
if (inifile_line_type (inifile->content[line][0]) == INIFILE_SECTION)
{
current_section = str_replace (current_section, inifile_section_name (inifile->content[line][0]));
}
else if (!str_casecmp(current_section, section))
{
t_uchar *temp_key=inifile_key_name (inifile->content[line][0]);
t_uchar *temp_value;
t_uchar *temp_comment;
if (str_casecmp (temp_key, key))
{
lim_free (0, temp_key);
continue;
}
temp_value = inifile_value (inifile->content[line][0]);
temp_comment = inifile_comment (inifile->content[line][0]);
rel_add_records (&result, rel_make_record (temp_value, temp_comment/*, lineno */, 0), 0);
lim_free (0, temp_key);
lim_free (0, temp_value);
lim_free (0, temp_comment);
}
}
lim_free (0, current_section);
return result;
}
/**
* \brief identify the type of a line from an ini file
* \param line a line to identify
* \return inifile_line_type_t
*/
inifile_line_type_t
inifile_line_type (t_uchar const *line)
{
int position;
int looking_for = 0;
t_uchar *temp;
/* shortcuts by regex. stuff efficiency ini files are small */
if ((temp = inifile_section_name (line)))
{
lim_free (0, temp);
return INIFILE_SECTION;
}
/* 0 -> ; # a-zA-Z
* 1 -> =
*/
for (position = 0; position < str_length (line); ++position)
{
switch (looking_for)
{
case 0:
if (line[position] == ';')
return INIFILE_COMMENT;
else if (line[position] == '#')
return INIFILE_COMMENT;
else if (('a' <= line[position] && line[position] <= 'z') ||
('A' <= line[position] && line[position] <= 'Z'))
looking_for = 1;
break;
case 1:
if (line[position] == '=')
return INIFILE_KEY;
break;
}
}
return INIFILE_COMMENT;
}
/**
* \brief create a ini file line
*/
static rel_record
inifile_make_line (t_uchar const *key, t_uchar const * value, t_uchar const * comment)
{
t_uchar * temp_line;
rel_record result;
if (!str_length (key))
temp_line = str_save (0, comment);
else if (str_length (comment))
temp_line = str_alloc_cat_many (0, key, "=", value, ";", comment, str_end);
else
temp_line = str_alloc_cat_many (0, key, "=", value, str_end);
result = rel_make_record (temp_line, 0);
lim_free (0, temp_line);
return result;
}
/**
* \brief add a new key to an inifile.
*
* the key is added at the last line of the last instance of the section in belongs in.
* \param inifile the file to update
* \param section the section the key belongs in ("" for no section).
* \param key the key name ("" for a literal comment)
* \param value the value for the key
* \param comment the comment to use
* \return void
*/
void
inifile_add_key (inifile_t *inifile, t_uchar const *section, t_uchar const *key, t_uchar const * value, t_uchar const * comment)
{
int candidate_insertion = 0;
int line;
t_uchar * current_section = str_save (0, "");
rel_for_each (inifile->content, line)
{
if (inifile_line_type (inifile->content[line][0]) == INIFILE_SECTION)
{
if (!str_casecmp (current_section, section))
candidate_insertion = line;
current_section = str_replace (current_section, inifile_section_name (inifile->content[line][0]));
}
}
if (!rel_n_records (inifile->content))
rel_add_records (&inifile->content, rel_make_record ("[]", 0), 0);
if (!str_casecmp (current_section, section))
candidate_insertion = rel_n_records (inifile->content);
if (str_length (section) && candidate_insertion == 0)
{
t_uchar * temp_section = str_alloc_cat_many (0, "[", section, "]", str_end);
rel_insert_records (&inifile->content, rel_n_records (inifile->content), rel_make_record (temp_section, 0), 0);
candidate_insertion = rel_n_records (inifile->content);
lim_free (0, temp_section);
}
rel_insert_records (&inifile->content, candidate_insertion, inifile_make_line (key, value, comment), 0);
lim_free (0, current_section);
}
/**
* \brief update an existing key with new value and comment
* \param inifile the inifile
* \param the key we are changing
* \param key the key to update
* \param index the index (from-0) of the keys values to change
* \param value the value to set
* \param comment the comment to set
*/
void
inifile_update_key (inifile_t *inifile, t_uchar const *section, t_uchar const *key, int index, t_uchar const * value, t_uchar const * comment)
{
int seen_keys = 0;
int line;
t_uchar * current_section = str_save (0, "");
rel_for_each (inifile->content, line)
{
if (inifile_line_type (inifile->content[line][0]) == INIFILE_SECTION)
{
current_section = str_replace (current_section, inifile_section_name (inifile->content[line][0]));
}
else if (!str_casecmp(current_section, section))
{
t_uchar *temp_key=inifile_key_name (inifile->content[line][0]);
if (str_casecmp (temp_key, key))
{
lim_free (0, temp_key);
continue;
}
/* matching key */
if (seen_keys++ == index)
{
/* assign it a value */
rel_replace_record (inifile->content, line,inifile_make_line (key, value, comment));
}
lim_free (0, temp_key);
}
}
lim_free (0, current_section);
}
/**
* \brief remove a key from an inifile
* \param inifile the inifile
* \param section the section we are looking for the key in
* \param key the key to remove
* \param index the index (from-0) of the keys values to remove (-1 for all)
*/
void
inifile_remove_key (inifile_t *inifile, t_uchar const *section, t_uchar const *key, int index)
{
int seen_keys = 0;
int line;
int line_to_remove = -1;
t_uchar * current_section = str_save (0, "");
/* factoring this out needs a keys_iterator built */
rel_for_each (inifile->content, line)
{
if (inifile_line_type (inifile->content[line][0]) == INIFILE_SECTION)
{
current_section = str_replace (current_section, inifile_section_name (inifile->content[line][0]));
}
else if (!str_casecmp(current_section, section))
{
t_uchar *temp_key=inifile_key_name (inifile->content[line][0]);
if (str_casecmp (temp_key, key))
{
lim_free (0, temp_key);
continue;
}
/* matching key */
if (seen_keys++ == index)
{
line_to_remove = line;
}
lim_free (0, temp_key);
}
}
lim_free (0, current_section);
if (line_to_remove > -1)
rel_remove_records (&inifile->content, line_to_remove, line_to_remove);
}
/**
* \brief retrieve a single-valued key, with an optional default value
*/
t_uchar *
inifile_get_single_string (inifile_t *inifile, t_uchar const * section, t_uchar const * key, t_uchar const * default_value)
{
rel_table values;
t_uchar *result;
values = inifile_get_key_values (inifile, section, key);
if (!rel_n_records (values))
result = str_save (0, default_value);
else
result = str_save (0, values[rel_n_records (values) - 1][0]);
rel_free_table (values);
return result;
}
/**
* \brief set a single-valued key, adding it if it doesn't exist
*/
void
inifile_set_single_string (inifile_t *inifile, t_uchar const * section, t_uchar const * key, t_uchar const * value, t_uchar const * comment)
{
rel_table values;
values = inifile_get_key_values (inifile, section, key);
if (!rel_n_records (values))
inifile_add_key (inifile, section, key, value, comment);
else
inifile_update_key (inifile, section, key, rel_n_records (values) - 1, value, comment);
rel_free_table (values);
}
syntax highlighted by Code2HTML, v. 0.9.1