/*
    INI LIBRARY

    Reading configuration from INI file
    and storing as a collection.

    Copyright (C) Dmitri Pal <dpal@redhat.com> 2009

    INI 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.

    INI 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.

    You should have received a copy of the GNU Lesser General Public License
    along with INI Library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "config.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "trace.h"
#include "collection.h"
#include "collection_tools.h"
#include "path_utils.h"
#include "ini_defines.h"
#include "ini_metadata.h"
#include "ini_config.h"

/* Temporarily move the parsing function here */
/* THIS FUNCTION WILL BE REMOVED AS SOON AS WE SWITCH TO THE NEW INTERFACE */
/* Reads a line from the file */
static int read_line(FILE *file,
                     char *buf,
                     int read_size,
                     char **key, char **value,
                     int *length,
                     int *ext_error)
{

    char *res;
    int len;
    char *buffer;
    int i;
    char *eq;

    TRACE_FLOW_STRING("read_line", "Entry");

    *ext_error = 0;

    buffer = buf;

    /* Get data from file */
    res = fgets(buffer, read_size - 1, file);
    if (res == NULL) {
        TRACE_ERROR_STRING("Read nothing", "");
        return RET_EOF;
    }

    /* Make sure the buffer is NULL terminated */
    buffer[read_size - 1] = '\0';

    len = strlen(buffer);
    if (len == 0) {
        TRACE_ERROR_STRING("Nothing was read.", "");
        return RET_EMPTY;
    }

    /* Added \r just in case we deal with Windows in future */
    if ((buffer[len - 1] != '\n') && (buffer[len - 1] != '\r')) {
        TRACE_ERROR_STRING("String it too big!", "");
        *ext_error = ERR_LONGDATA;
        return RET_ERROR;
    }

    /* Ingnore comments */
    if ((*buffer == ';') || (*buffer == '#')) {
        TRACE_FLOW_STRING("Comment", buf);
        return RET_COMMENT;
    }

    TRACE_INFO_STRING("BUFFER before trimming:", buffer);

    /* Trucate trailing spaces and CRs */
    /* Make sure not to step before the beginning */
    while (len && isspace(buffer[len - 1])) {
        buffer[len - 1] = '\0';
        len--;
    }

    TRACE_INFO_STRING("BUFFER after trimming trailing spaces:", buffer);

    /* Trucate leading spaces  */
    while (isspace(*buffer)) {
        buffer++;
        len--;
    }

    TRACE_INFO_STRING("BUFFER after trimming leading spaces:", buffer);
    TRACE_INFO_NUMBER("BUFFER length:", len);

    /* Empty line */
    if (len == 0) {
        TRACE_FLOW_STRING("Empty line", buf);
        return RET_EMPTY;
    }

    /* Section */
    if (*buffer == '[') {
        if (buffer[len-1] != ']') {
            TRACE_ERROR_STRING("Invalid format for section", buf);
            *ext_error = ERR_NOCLOSESEC;
            return RET_ERROR;
        }
        buffer++;
        len--;
        while (isspace(*buffer)) {
            buffer++;
            len--;
        }
        if (len == 0) {
            TRACE_ERROR_STRING("Invalid format for section", buf);
            *ext_error = ERR_NOSECTION;
            return RET_ERROR;
        }

        buffer[len - 1] = '\0';
        len--;
        while (isspace(buffer[len - 1])) {
            buffer[len - 1] = '\0';
            len--;
        }
        if (len >= MAX_KEY) {
            TRACE_ERROR_STRING("Section name is too long", buf);
            *ext_error = ERR_SECTIONLONG;
            return RET_ERROR;
        }

        *key = buffer;
        return RET_SECTION;
    }

    /* Assume we are dealing with the K-V here */
    /* Find "=" */
    eq = strchr(buffer, '=');
    if (eq == NULL) {
        TRACE_ERROR_STRING("No equal sign", buf);
        *ext_error = ERR_NOEQUAL;
        return RET_INVALID;
    }

    len -= eq-buffer;

    /* Strip spaces around "=" */
    i = eq - buffer - 1;
    while ((i >= 0) && isspace(buffer[i])) i--;
    if (i < 0) {
        TRACE_ERROR_STRING("No key", buf);
        *ext_error = ERR_NOKEY;
        return RET_INVALID;
    }

    /* Copy key into provided buffer */
    if(i >= MAX_KEY) {
        TRACE_ERROR_STRING("Key name is too long", buf);
        *ext_error = ERR_LONGKEY;
        return RET_INVALID;
    }
    *key = buffer;
    buffer[i + 1] = '\0';
    TRACE_INFO_STRING("KEY:", *key);

    eq++;
    len--;
    while (isspace(*eq)) {
        eq++;
        len--;
    }

    *value = eq;
    /* Make sure we include trailing 0 into data */
    *length = len + 1;

    TRACE_INFO_STRING("VALUE:", *value);
    TRACE_INFO_NUMBER("LENGTH:", *length);

    TRACE_FLOW_STRING("read_line", "Exit");
    return RET_PAIR;
}

/************************************************************/
/* REMOVE FUNCTION ABOVE                                    */
/************************************************************/


/***************************************************************************/
/* Function to read single ini file and pupulate
 * the provided collection with subcollcetions from the file */
static int ini_to_collection(FILE *file,
                             const char *config_filename,
                             struct collection_item *ini_config,
                             int error_level,
                             struct collection_item **error_list,
                             struct collection_item *lines)
{
    int error;
    int status;
    int section_count = 0;
    char *key = NULL;
    char *value = NULL;
    struct collection_item *current_section = NULL;
    int length;
    int ext_err = -1;
    struct parse_error pe;
    int line = 0;
    int created = 0;
    char buf[BUFFER_SIZE+1];


    TRACE_FLOW_STRING("ini_to_collection", "Entry");

    /* Open the collection of errors */
    if (error_list != NULL) {
        *error_list = NULL;
        error = col_create_collection(error_list, INI_ERROR, COL_CLASS_INI_PERROR);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to create error collection", error);
            return error;
        }
        /* Add file name as the first item */
        error = col_add_str_property(*error_list, NULL, INI_ERROR_NAME, config_filename, 0);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to and name to collection", error);
            col_destroy_collection(*error_list);
            return error;
        }
        created = 1;
    }

    /* Read file lines */
    while (1) {
        /* Always read one less than the buffer */
        status = read_line(file, buf, BUFFER_SIZE+1, &key, &value, &length, &ext_err);
        if (status == RET_EOF) break;

        line++;

        switch (status) {
        case RET_PAIR:

#ifdef HAVE_VALIDATION

            /* Add line to the collection of lines.
             * It is pretty safe in this case to just type cast the value to
             * int32_t since it is unrealistic that ini file will ever have
             * so many lines.
             */
            if (lines) {
                error = col_add_int_property(lines, NULL, key, (int32_t)line);
                if (error) {
                    TRACE_ERROR_NUMBER("Failed to add line to line collection", error);
                    col_destroy_collection(current_section);
                    if (created) {
                        col_destroy_collection(*error_list);
                        *error_list = NULL;
                    }
                    return error;
                }
            }

#endif /* HAVE_VALIDATION */

            /* Do we have a section at the top of the file ? */
            if (section_count == 0) {
                /* Check if collection already exists */
                error = col_get_collection_reference(ini_config, &current_section,
                                                     INI_DEFAULT_SECTION);
                if (error != EOK) {
                    /* Create default collection */
                    if ((error = col_create_collection(&current_section,
                                                       INI_DEFAULT_SECTION,
                                                       COL_CLASS_INI_SECTION)) ||
                        (error = col_add_collection_to_collection(ini_config,
                                                                  NULL,NULL,
                                                                  current_section,
                                                                  COL_ADD_MODE_REFERENCE))) {
                        TRACE_ERROR_NUMBER("Failed to create collection", error);
                        col_destroy_collection(current_section);
                        if (created) {
                            col_destroy_collection(*error_list);
                            *error_list = NULL;
                        }
                        return error;
                    }
                }
                section_count++;
            }

            /* Put value into the collection */
            error = col_insert_str_property(current_section,
                                            NULL,
                                            COL_DSP_END,
                                            NULL,
                                            0,
                                            COL_INSERT_DUPOVER,
                                            key,
                                            value,
                                            length);
            if (error != EOK) {
                TRACE_ERROR_NUMBER("Failed to add pair to collection", error);
                col_destroy_collection(current_section);
                if (created) {
                    col_destroy_collection(*error_list);
                    *error_list = NULL;
                }
                return error;
            }
            break;

        case RET_SECTION:

#ifdef HAVE_VALIDATION

            /* Add line to the collection of lines */
            if (lines) {
                /* For easier search make line numbers for the sections negative.
                 * This would allow differentiating sections and attributes.
                 * It is pretty safe in this case to just type cast the value to
                 * int32_t since it is unrealistic that ini file will ever have
                 * so many lines.
                 */
                error = col_add_int_property(lines, NULL, key, (int32_t)(-1 * line));
                if (error) {
                    TRACE_ERROR_NUMBER("Failed to add line to line collection", error);
                    col_destroy_collection(current_section);
                    if (created) {
                        col_destroy_collection(*error_list);
                        *error_list = NULL;
                    }
                    return error;
                }
            }

#endif /* HAVE_VALIDATION */

            /* Read a new section */
            col_destroy_collection(current_section);
            current_section = NULL;

            error = col_get_collection_reference(ini_config, &current_section, key);
            if (error != EOK) {
                /* Create default collection */
                if ((error = col_create_collection(&current_section, key,
                                                   COL_CLASS_INI_SECTION)) ||
                    (error = col_add_collection_to_collection(ini_config,
                                                              NULL, NULL,
                                                              current_section,
                                                              COL_ADD_MODE_REFERENCE))) {
                    TRACE_ERROR_NUMBER("Failed to add collection", error);
                    col_destroy_collection(current_section);
                    if (created) {
                        col_destroy_collection(*error_list);
                        *error_list = NULL;
                    }
                    return error;
                }
            }
            section_count++;
            break;

        case RET_EMPTY:
            TRACE_INFO_STRING("Empty string", "");
            break;

        case RET_COMMENT:
            TRACE_INFO_STRING("Comment", "");
            break;

        case RET_ERROR:
            /* Try to add to the error list only if it is present */
            if (error_list) {
                pe.line = line;
                pe.error = ext_err;
                error = col_add_binary_property(*error_list, NULL,
                                                ERROR_TXT, &pe, sizeof(pe));
                if (error) {
                    TRACE_ERROR_NUMBER("Failed to add error to collection",
                                       error);
                    col_destroy_collection(current_section);
                    if (created) {
                        col_destroy_collection(*error_list);
                        *error_list = NULL;
                    }
                    return error;
                }
            }

            /* Exit if there was an error parsing file */
            if (error_level != INI_STOP_ON_NONE) {
                TRACE_ERROR_STRING("Invalid format of the file", "");
                col_destroy_collection(current_section);
                return EIO;
            }
            break;

        case RET_INVALID:
        default:
            /* Try to add to the error list only if it is present */
            if (error_list) {
                pe.line = line;
                pe.error = ext_err;
                error = col_add_binary_property(*error_list, NULL,
                                                WARNING_TXT, &pe, sizeof(pe));
                if (error) {
                    TRACE_ERROR_NUMBER("Failed to add warning to collection",
                                       error);
                    col_destroy_collection(current_section);
                    if (created) {
                        col_destroy_collection(*error_list);
                        *error_list = NULL;
                    }
                    return error;
                }
            }

            /* Exit if we are told to exit on warnings */
            if (error_level == INI_STOP_ON_ANY) {
                TRACE_ERROR_STRING("Invalid format of the file", "");
                if (created) col_destroy_collection(current_section);
                return EIO;
            }
            TRACE_ERROR_STRING("Invalid string", "");
            break;
        }
        ext_err = -1;
    }

    /* Note: File is not closed on this level any more.
     * It opened on the level above, checked and closed there.
     * It is not the responsibility of this function to close
     * file any more.
     */

    COL_DEBUG_COLLECTION(ini_config);

    col_destroy_collection(current_section);

    COL_DEBUG_COLLECTION(ini_config);

    TRACE_FLOW_STRING("ini_to_collection", "Success Exit");

    return EOK;
}

/*********************************************************************/
/* Function to free configuration */
void free_ini_config(struct collection_item *ini_config)
{
    TRACE_FLOW_STRING("free_ini_config", "Entry");
    col_destroy_collection(ini_config);
    TRACE_FLOW_STRING("free_ini_config", "Exit");
}

/* Function to free configuration error list */
void free_ini_config_errors(struct collection_item *error_set)
{
    TRACE_FLOW_STRING("free_ini_config_errors", "Entry");
    col_destroy_collection(error_set);
    TRACE_FLOW_STRING("free_ini_config_errors", "Exit");
}

#ifdef HAVE_VALIDATION

/* Function to free configuration lines list.
 *
 * The following doxygen description is moved here.
 * When the function gets exposed move it into
 * the header file.
 */
/**
 * @brief Function to free lines object.
 *
 * EXPERIMENTAL. Reserved for future use.
 *
 * @param[in] lines       Lines object.
 *
 */

void free_ini_config_lines(struct collection_item *lines)
{
    TRACE_FLOW_STRING("free_ini_config_lines", "Entry");
    col_destroy_collection(lines);
    TRACE_FLOW_STRING("free_ini_config_lines", "Exit");
}

#endif /* HAVE_VALIDATION */


/* Read configuration information from a file */
int config_from_file(const char *application,
                     const char *config_filename,
                     struct collection_item **ini_config,
                     int error_level,
                     struct collection_item **error_list)
{
    int error;

    TRACE_FLOW_STRING("config_from_file", "Entry");
    error = config_from_file_with_metadata(application,
                                           config_filename,
                                           ini_config,
                                           error_level,
                                           error_list,
                                           0,
                                           NULL);
    TRACE_FLOW_NUMBER("config_from_file. Returns", error);
    return error;
}

/* Read configuration information from a file descriptor */
int config_from_fd(const char *application,
                   int fd,
                   const char *config_source,
                   struct collection_item **ini_config,
                   int error_level,
                   struct collection_item **error_list)
{
    int error;

    TRACE_FLOW_STRING("config_from_fd", "Entry");
    error = config_from_fd_with_metadata(application,
                                         fd,
                                         config_source,
                                         ini_config,
                                         error_level,
                                         error_list,
                                         0,
                                         NULL);
    TRACE_FLOW_NUMBER("config_from_fd. Returns", error);
    return error;
}



/* Low level function that prepares the collection
 * and calls parser.
 */
static int config_with_metadata(const char *application,
                                FILE *config_file,
                                const char *config_source,
                                struct collection_item **ini_config,
                                int error_level,
                                struct collection_item **error_list,
                                uint32_t metaflags,
                                struct collection_item **metadata)
{
    int error;
    int created = 0;
    struct collection_item *lines = NULL;

#ifdef HAVE_VALIDATION
    int created_lines = 0;
#endif

    TRACE_FLOW_STRING("config_from_file", "Entry");

    /* Now we check arguments in the calling functions. */

    /* Create collection if needed */
    if (*ini_config == NULL) {
        error = col_create_collection(ini_config,
                                      application,
                                      COL_CLASS_INI_CONFIG);
        if (error != EOK) {
            TRACE_ERROR_NUMBER("Failed to create collection", error);
            return error;
        }
        created = 1;
    }
    /* Is the collection of the right class? */
    else if (((col_is_of_class(*ini_config, COL_CLASS_INI_CONFIG))== 0) &&
             ((col_is_of_class(*ini_config, COL_CLASS_INI_META))== 0)) {
        TRACE_ERROR_NUMBER("Wrong collection type", EINVAL);
        return EINVAL;
    }

#ifdef HAVE_VALIDATION
    /* This code is preserved for future use */
    error = col_create_collection(lines,
                                    application,
                                    COL_CLASS_INI_LINES);
    if (error != EOK) {
        TRACE_ERROR_NUMBER("Failed to create collection", error);
        if (created) {
            col_destroy_collection(*ini_config);
            *ini_config = NULL;
        }
        return error;
    }
    created_lines = 1;
#else
    /* Until we implement validation do not read the lines. */
    lines = NULL;
#endif /* HAVE_VALIDATION */

    /* Do the actual work - for now do not read lines.*/
    error = ini_to_collection(config_file, config_source,
                              *ini_config, error_level,
                              error_list, lines);
    /* In case of error when we created collection - delete it */
    if (error && created) {
        col_destroy_collection(*ini_config);
        *ini_config = NULL;
    }

    /* FIXME - put lines collection into the metadata */

    TRACE_FLOW_NUMBER("config_from_file. Returns", error);
    return error;
}

/* Function to read the ini file from fd
 * with meta data.
 */
int config_from_fd_with_metadata(const char *application,
                                 int ext_fd,
                                 const char *config_filename,
                                 struct collection_item **ini_config,
                                 int error_level,
                                 struct collection_item **error_list,
                                 uint32_t metaflags,
                                 struct collection_item **metadata)
{
    int error = EOK;
    int file_error = EOK;
    int save_error = 0;
    int fd = -1;
    FILE *config_file = NULL;
    char abs_name[PATH_MAX + 1];
    char buff[CONVERSION_BUFFER];

    TRACE_FLOW_STRING("config_from_fd_with_metadata", "Entry");

    /* We need to check arguments before we can move on,
     * and start allocating memory.
     */
    if ((ini_config == NULL) ||
        (application == NULL)) {
        TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
        return EINVAL;
    }

    /* Prepare meta data */
    error = prepare_metadata(metaflags, metadata, &save_error);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to prepare metadata", error);
        return error;
    }

    errno = 0;

    if (ext_fd == -1) {
        /* No file descriptor so use name */
        config_file = fopen(config_filename, "r");
    }
    else {
        /* Create a copy of the descriptor so that we can close it if needed */
        fd = dup(ext_fd);
        if (fd != -1) config_file = fdopen(fd, "r");
    }
    file_error = errno;

    if (save_error) {
        /* Record the result of the open file operation in metadata */
        snprintf(buff, CONVERSION_BUFFER, "%d", file_error);
        error = col_add_str_property(*metadata,
                                     INI_META_SEC_ERROR,
                                     INI_META_KEY_READ_ERROR,
                                     buff,
                                     0);
        if (error) {
            /* Something is really wrong if we failed here */
            TRACE_ERROR_NUMBER("Failed to save file open error", error);
            if (config_file) fclose(config_file);
            return error;
        }
    }

    if(!config_file) {
        TRACE_ERROR_NUMBER("Failed to open file", file_error);
        return file_error;
    }

    /* Normalize path for reporting purposes */
    error = make_normalized_absolute_path(abs_name,
                                          PATH_MAX,
                                          config_filename);
    if(error) {
        TRACE_ERROR_NUMBER("Failed to resolve path", error);
        fclose(config_file);
        return error;
    }


    if (metadata) {
        /* Collect meta data before actually parsing the file */
        error = collect_metadata(metaflags,
                                 metadata,
                                 config_file,
                                 abs_name);
        if(error) {
            TRACE_ERROR_NUMBER("Failed to collect metadata", error);
            fclose(config_file);
            return error;
        }
    }

    if (!(metaflags & INI_META_ACTION_NOPARSE)) {
        /* Parse data if needed */
        error = config_with_metadata(application,
                                     config_file,
                                     abs_name,
                                     ini_config,
                                     error_level,
                                     error_list,
                                     metaflags,
                                     metadata);
    }

    /* We opened the file we close it */
    fclose(config_file);

    TRACE_FLOW_NUMBER("config_from_fd_with_metadata. Returns", error);
    return error;
}

/* Function to read the ini file with metadata
 * using file name.
 */
int config_from_file_with_metadata(const char *application,
                                   const char *config_filename,
                                   struct collection_item **ini_config,
                                   int error_level,
                                   struct collection_item **error_list,
                                   uint32_t metaflags,
                                   struct collection_item **metadata)
{
    int error = EOK;
    TRACE_FLOW_STRING("config_from_file_with_metadata", "Entry");

    error = config_from_fd_with_metadata(application,
                                         -1,
                                         config_filename,
                                         ini_config,
                                         error_level,
                                         error_list,
                                         metaflags,
                                         metadata);

    TRACE_FLOW_STRING("config_from_file_with_metadata", "Exit");
    return error;
}


/* Read default config file and then overwrite it with a specific one
 * from the directory */
int config_for_app_with_metadata(const char *application,
                                 const char *config_file,
                                 const char *config_dir,
                                 struct collection_item **ini_config,
                                 int error_level,
                                 struct collection_item **error_set,
                                 uint32_t metaflags,
                                 struct collection_item **meta_default,
                                 struct collection_item **meta_appini)
{
    int error = EOK;
    char *file_name;
    struct collection_item *error_list_common = NULL;
    struct collection_item *error_list_specific = NULL;
    struct collection_item **pass_common = NULL;
    struct collection_item **pass_specific = NULL;
    int created = 0;
    int tried = 0;
    int noents = 0;

    TRACE_FLOW_STRING("config_for_app", "Entry");

    if (ini_config == NULL) {
        TRACE_ERROR_NUMBER("Invalid parameter", EINVAL);
        return EINVAL;
    }

    if ((config_file == NULL) && (config_dir == NULL)) {
        TRACE_ERROR_NUMBER("Noop call of the function is invalid", EINVAL);
        return EINVAL;
    }

    /* Prepare error collection pointers */
    if (error_set != NULL) {
        TRACE_INFO_STRING("Error set is not NULL", "preparing error set");
        pass_common = &error_list_common;
        pass_specific = &error_list_specific;
        *error_set = NULL;
        /* Construct the overarching error collection */
        error = col_create_collection(error_set,
                                      FILE_ERROR_SET,
                                      COL_CLASS_INI_PESET);
        if (error != EOK) {
            TRACE_ERROR_NUMBER("Failed to create collection", error);
            return error;
        }
    }
    else {
        TRACE_INFO_STRING("No error set. Errors will not be captured", "");
        pass_common = NULL;
        pass_specific = NULL;
    }

    /* Create collection if needed */
    if (*ini_config == NULL) {
        TRACE_INFO_STRING("New config collection. Allocate.", "");
        error = col_create_collection(ini_config,
                                      application,
                                      COL_CLASS_INI_CONFIG);
        if (error != EOK) {
            TRACE_ERROR_NUMBER("Failed to create collection", error);
            if (error_set) {
                col_destroy_collection(*error_set);
                *error_set = NULL;
            }
            return error;
        }
        created = 1;
    }
    /* Is the collection of the right class? */
    else if ((col_is_of_class(*ini_config, COL_CLASS_INI_CONFIG) == 0) &&
             (col_is_of_class(*ini_config, COL_CLASS_INI_META) == 0)) {
        TRACE_ERROR_NUMBER("Wrong collection type", EINVAL);
        return EINVAL;
    }

    /* Read master file */
    if (config_file != NULL) {
        TRACE_INFO_STRING("Reading master file:", config_file);
        /* Get configuration information from the file */
        error = config_from_file_with_metadata(application,
                                               config_file,
                                               ini_config,
                                               error_level,
                                               pass_common,
                                               metaflags,
                                               meta_default);
        tried++;
        /* ENOENT and EOK are Ok */
        if (error) {
            if (error != ENOENT) {
                TRACE_ERROR_NUMBER("Failed to read master file", error);
                /* In case of error when we created collection - delete it */
                if(error && created) {
                    col_destroy_collection(*ini_config);
                    *ini_config = NULL;
                }
                /* We do not clear the error_set here */
                return error;
            }
            else noents++;
        }
        /* Add error results if any to the overarching error collection */
        if ((pass_common != NULL) && (*pass_common != NULL)) {
            TRACE_INFO_STRING("Process errors resulting from file:", config_file);
            error = col_add_collection_to_collection(*error_set, NULL, NULL,
                                                     *pass_common,
                                                     COL_ADD_MODE_EMBED);
            if (error) {
                if (created) {
                    col_destroy_collection(*ini_config);
                    *ini_config = NULL;
                }
                if (error_set) {
                    col_destroy_collection(*error_set);
                    *error_set = NULL;
                }
                TRACE_ERROR_NUMBER("Failed to add error collection to another error collection", error);
                return error;
            }
        }
    }

    if (config_dir != NULL) {
        /* Get specific application file */
        file_name = malloc(strlen(config_dir) + strlen(application) + NAME_OVERHEAD);
        if (file_name == NULL) {
            error = ENOMEM;
            TRACE_ERROR_NUMBER("Failed to allocate memory for file name", error);
            /* In case of error when we created collection - delete it */
            if(created) {
                col_destroy_collection(*ini_config);
                *ini_config = NULL;
            }
            if (error_set) {
                col_destroy_collection(*error_set);
                *error_set = NULL;
            }
            return error;
        }

        /* It is safe to use sprintf() here */
        sprintf(file_name, "%s%s%s.conf", config_dir, SLASH, application);
        TRACE_INFO_STRING("Opening file:", file_name);
        /* Read specific file */
        error = config_from_file_with_metadata(application,
                                               file_name,
                                               ini_config,
                                               error_level,
                                               pass_specific,
                                               metaflags,
                                               meta_appini);
        tried++;
        free(file_name);
        /* ENOENT and EOK are Ok */
        if (error) {
            if (error != ENOENT) {
                TRACE_ERROR_NUMBER("Failed to read specific application file", error);
                /* In case of error when we created collection - delete it */
                if (error && created) {
                    col_destroy_collection(*ini_config);
                    *ini_config = NULL;
                }
                /* We do not clear the error_set here */
                return error;
            }
            else noents++;
        }
        /* Add error results if any to the overarching error collection */
        if ((pass_specific != NULL) && (*pass_specific != NULL)) {
            error = col_add_collection_to_collection(*error_set, NULL, NULL,
                                                     *pass_specific,
                                                     COL_ADD_MODE_EMBED);
            if (error) {
                if (created) {
                    col_destroy_collection(*ini_config);
                    *ini_config = NULL;
                }
                if (error_set) {
                    col_destroy_collection(*error_set);
                    *error_set = NULL;
                }
                TRACE_ERROR_NUMBER("Failed to add error collection to another error collection", error);
                return error;
            }
        }
    }

    /* If we failed to read or access file as many
     * times as we tried and we told to stop on any errors
     * we should report an error.
     */
    TRACE_INFO_NUMBER("Tried:", tried);
    TRACE_INFO_NUMBER("Noents:", noents);

    if ((tried == noents) && (error_level == INI_STOP_ON_ANY)) {
        TRACE_ERROR_NUMBER("Fail to read or access all the files tried", ENOENT);
        if (created) {
            col_destroy_collection(*ini_config);
            *ini_config = NULL;
        }
        if (error_set) {
            col_destroy_collection(*error_set);
            *error_set = NULL;
        }
        return ENOENT;
    }

    TRACE_FLOW_STRING("config_to_collection", "Exit");
    return EOK;
}


/* Function to return configuration data
 * for the application without meta data.
 */
int config_for_app(const char *application,
                   const char *config_file,
                   const char *config_dir,
                   struct collection_item **ini_config,
                   int error_level,
                   struct collection_item **error_set)
{
    int error = EOK;
    TRACE_FLOW_STRING("config_for_app", "Entry");

    error = config_for_app_with_metadata(application,
                                         config_file,
                                         config_dir,
                                         ini_config,
                                         error_level,
                                         error_set,
                                         0,
                                         NULL,
                                         NULL);

    TRACE_FLOW_NUMBER("config_for_app. Returning", error);
    return error;
}
