//%LICENSE////////////////////////////////////////////////////////////////
//
// Licensed to The Open Group (TOG) under one or more contributor license
// agreements.  Refer to the OpenPegasusNOTICE.txt file distributed with
// this work for additional information regarding copyright ownership.
// Each contributor licenses this file to you under the OpenPegasus Open
// Source License; you may not use this file except in compliance with the
// License.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////
//
//%/////////////////////////////////////////////////////////////////////////////


#include <cctype>
#include <fstream>
#include <Pegasus/Common/FileSystem.h>
#include <Pegasus/Common/HashTable.h>
#include <Pegasus/Common/Tracer.h>
#include <Pegasus/Common/Executor.h>

#include "ConfigExceptions.h"
#include "ConfigFile.h"

PEGASUS_USING_STD;

PEGASUS_NAMESPACE_BEGIN


////////////////////////////////////////////////////////////////////////////////
//
//  ConfigFile Class
//
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// ConfigTable
////////////////////////////////////////////////////////////////////////////////

typedef HashTable<String, String, EqualFunc<String>, HashFunc<String> > Table;

struct ConfigTable
{
    Table table;
};


/*
    Config file header information
*/
static const char* ConfigHeader [] = {
    "########################################################################",
    "##                                                                    ##",
    "##                  CIM Server configuration file                     ##",
    "##                                                                    ##",
    "########################################################################",
    "",
    "########################################################################",
    "#                                                                      #",
    "# The configuration in this file is loaded by the CIM Server at        #",
    "# start-up.  This file is updated by the CIM Server when the           #",
    "# configuration changes.                                               #",
    "#                                                                      #",
    "# Do not edit this file directly.  Instead, use the cimconfig command  #",
    "# to update the CIM Server configuration.                              #",
    "#                                                                      #",
    "########################################################################",
    ""
};

static const int HEADER_SIZE = sizeof(ConfigHeader) / sizeof(ConfigHeader[0]);


/**
    Constructor.
*/
ConfigFile::ConfigFile (const String& fileName)
{
    _configFile = fileName;

    _configBackupFile = fileName + ".bak";
}

/**
    Destructor.
*/
ConfigFile::~ConfigFile ()
{
}


/**
    Get the name of the configuration file.
*/
String ConfigFile::getFileName () const
{
    return _configFile;
}


/**
    Load the properties from the config file.
*/
void ConfigFile::load(ConfigTable* confTable)
{
    String line;

    //
    // Delete the backup configuration file
    //

    if (FileSystem::exists(_configBackupFile))
    {
        Executor::removeFile(_configBackupFile.getCString());
    }

    //
    // Open the config file
    //
    ifstream ifs(_configFile.getCString());

    if (!ifs)
    {
        return;
    }

    //
    // Read each line of the file
    //
    for (Uint32 lineNumber = 1; GetLine(ifs, line); lineNumber++)
    {
        // Get the property name and value

        //
        // Skip leading whitespace
        //
        const Char16* p = line.getChar16Data();

        while (*p && isspace(*p))
        {
            p++;
        }

        if (!*p)
        {
            continue;
        }

        //
        // Skip comment lines
        //
        if (*p == '#')
        {
            continue;
        }

        //
        // Get the property name
        //
        String name;

        if (!(isalpha(*p) || *p == '_'))
        {
            ifs.close();
            throw ConfigFileSyntaxError(_configFile, lineNumber);
        }

        name.append(*p++);

        while (isalnum(*p) || *p == '_')
        {
            name.append(*p++);
        }

        //
        // Skip whitespace after property name
        //
        while (*p && isspace(*p))
        {
            p++;
        }

        //
        // Expect an equal sign
        //
        if (*p != '=')
        {
            ifs.close();
            throw ConfigFileSyntaxError(_configFile, lineNumber);
        }

        p++;

        //
        // Skip whitespace after equal sign
        //
        while (*p && isspace(*p))
        {
            p++;
        }

        //
        // Get the value
        //
        String value;

        while (*p)
        {
            value.append(*p++);
        }

        //
        // Store the property name and value in the table
        //
        if (!confTable->table.insert(name, value))
        {
            //
            // Duplicate property, ignore the new property value.
            // FUTURE: Log this message in a log file.
            //
            PEG_TRACE((TRC_CONFIG, Tracer::LEVEL2,
                "Duplicate property '%s', value '%s' is ignored.",
                (const char*)name.getCString(),
                (const char*)value.getCString()));

        }
    }

    ifs.close();
}


/**
    Save the properties to the config file.
*/
void ConfigFile::save(ConfigTable* confTable)
{
    //
    // Delete the backup configuration file
    //
    if (FileSystem::exists(_configBackupFile))
    {
        Executor::removeFile(_configBackupFile.getCString());
    }

    //
    // Rename the configuration file as a backup file
    //

    if (FileSystem::exists(_configFile))
    {
        if (Executor::renameFile(
            _configFile.getCString(), _configBackupFile.getCString()) != 0)
        {
            throw CannotRenameFile(_configFile);
        }
    }

    //
    // Open the config file for writing
    //

    FILE* ofs = Executor::openFile(_configFile.getCString(), 'w');

    if (!ofs)
    {
        throw CannotOpenFile(_configFile);
    }

    //
    // Write config file header information
    //

    for (int index = 0; index < HEADER_SIZE; index++)
    {
        fputs(ConfigHeader[index], ofs);
        fputc('\n', ofs);
    }

    //
    // Save config properties and values to the file
    //

    for (Table::Iterator i = confTable->table.start(); i; i++)
    {
        CString key = i.key().getCString();
        CString value = i.value().getCString();
        fprintf(ofs, "%s=%s\n", (const char*)key, (const char*)value);
    }

    fclose(ofs);

#if !defined(PEGASUS_OS_TYPE_WINDOWS)
    // Note:  The Executor process sets the permissions to 0644 when it
    // opens the config file for writing.
    if (Executor::detectExecutor() != 0)
    {
        //
        // Set permissions on the config file to 0644
        //
        if (!FileSystem::changeFilePermissions(
                _configFile,
                (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))    // set 0644
        {
            throw CannotChangeFilePerm(_configFile);
        }
    }
#endif
}


/**
    Replace the properties in the config file with the properties from
    the given file.
*/
void ConfigFile::replace (const String& fileName)
{
    //
    // Open the given config file for reading
    //

    FILE* ifs = fopen(fileName.getCString(), "r");

    if (!ifs)
    {
        throw CannotOpenFile(fileName);
    }

    //
    // Delete the backup configuration file
    //

    if (FileSystem::exists(_configBackupFile))
    {
        Executor::removeFile(_configBackupFile.getCString());
    }

    //
    // Rename the existing config file as a backup file
    //

    if (FileSystem::exists(_configFile))
    {
        if (Executor::renameFile(
            _configFile.getCString(), _configBackupFile.getCString()) != 0)
        {
            fclose(ifs);
            throw CannotRenameFile(_configFile);
        }
    }

    //
    // Open the existing config file for writing
    //

    FILE* ofs = Executor::openFile(_configFile.getCString(), 'w');

    if (!ofs)
    {
        fclose(ifs);
        throw CannotOpenFile(_configFile);
    }

    //
    // Read each line of the new file and write to the config file.
    //

    char buffer[4096];

    while ((fgets(buffer, sizeof(buffer), ifs)) != NULL)
        fputs(buffer, ofs);

    //
    // Close the file handles
    //

    fclose(ifs);
    fclose(ofs);

#if !defined(PEGASUS_ENABLE_PRIVILEGE_SEPARATION)
# if !defined(PEGASUS_OS_TYPE_WINDOWS)
    //
    // Set permissions on the config file to 0644
    //
    if (!FileSystem::changeFilePermissions(_configFile,
        (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))    // set 0644
    {
        throw CannotChangeFilePerm(_configFile);
    }
# endif
#endif /* PEGASUS_ENABLE_PRIVILEGE_SEPARATION */
}

PEGASUS_NAMESPACE_END
