PSSettingsPtr PSSettingsConfigurator::ReadSettingsFromFile()

in host/cxpslib/pssettingsconfigurator.cpp [19:212]


PSSettingsPtr PSSettingsConfigurator::ReadSettingsFromFile(
    bool& fileUnavailable,
    std::string& knownToFailHeader,
    std::string& knownToFailContent)
{
    fileUnavailable = true;

    if (!boost::filesystem::exists(m_settingsLckFilePath) ||
        !boost::filesystem::exists(m_settingsFilePath))
    {
        return PSSettingsPtr();
    }

    fileUnavailable = false;

    boost::system::error_code ec;
    time_t t = boost::filesystem::last_write_time(m_settingsFilePath, ec);
    if (ec)
    {
        t = 0;
    }

    if (!t && m_MTime == t)
    {
        return PSSettingsPtr();
    }

    std::string headerStr;
    std::string settingsFileContent;

    // TODO-SanKumar-2002: This constructor throws, if the lock file is not
    // present. Instead only expect the actual setting file to present, while
    // creating this file, if not present.
    boost::interprocess::file_lock flock(m_settingsLckFilePath.string().c_str());

    {
        boost::interprocess::sharable_lock<boost::interprocess::file_lock> sharableFlock(flock);

        // TODO-SanKumar-2002: This lock is not cancellable, which could be
        // replaced with Timed sharable_lock. The problem is that the lock seems
        // to be spinning repeatedly instead of blocking. Gotta check before usage.

        std::ifstream file;
        file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        file.open(m_settingsFilePath.string().c_str(), std::ios_base::binary);

        if (m_serverOptions->psSettingsIncludesHeader())
        {
            std::getline(file, headerStr);
        }

        std::size_t sz = static_cast<std::size_t>(boost::filesystem::file_size(m_settingsFilePath));
        sz -= file.tellg();
        settingsFileContent.resize(sz, '\0');
        file.read(&settingsFileContent[0], sz);
    }

    if (headerStr == knownToFailHeader &&
        settingsFileContent == knownToFailContent)
    {
        // By doing this precheck, we avoid repeated logging for a known
        // bad file, which will keep on failing.

        return PSSettingsPtr();

        // TODO-SanKumar-2002: Rate controlled log (say once in 10/25 times) in
        // both +ve and -ve cases.
    }

    if ((m_serverOptions->psSettingsIncludesHeader() && headerStr.empty()) ||
        (settingsFileContent.empty()))
    {
        CXPS_LOG_ERROR(AT_LOC << "Header line or content missing in the cache settings file");

        knownToFailHeader = headerStr;
        knownToFailContent = settingsFileContent;
        return PSSettingsPtr();
    }

    if ((m_serverOptions->psSettingsIncludesHeader()) &&
        (m_serverOptions->psSettingsEnforceMajorVersionCheck() ||
         m_serverOptions->psSettingsVerifyHeader()))
    {
        CacheDataHeader parsedHeader;
        try
        {
            parsedHeader =
                JSON::consumer<CacheDataHeader>::convert(headerStr, true); // std::move() candidate
        }
        catch (const std::exception & ex)
        {
            CXPS_LOG_ERROR(AT_LOC <<
                "Json serialization failed for the header in the cached settings file - " << ex.what());

            knownToFailHeader = headerStr;
            knownToFailContent = settingsFileContent;
            return PSSettingsPtr();
        }

        if (m_serverOptions->psSettingsEnforceMajorVersionCheck())
        {
            if (parsedHeader.Version.empty())
            {
                CXPS_LOG_ERROR(AT_LOC << "Empty version found in the cached settings file");

                knownToFailHeader = headerStr;
                knownToFailContent = settingsFileContent;
                return PSSettingsPtr();
            }

            std::vector<std::string> versionParts;
            boost::split(versionParts, parsedHeader.Version, boost::is_any_of("."));

            int majorVersion = boost::lexical_cast<int>(versionParts[0]);

            int minorVersion = 0;
            if (versionParts.size() != 1)
            {
                minorVersion = boost::lexical_cast<int>(versionParts[1]);
            }

            if (majorVersion != CacheDataHeader::CURRENT_CACHED_DATA_MAJOR_VERSION)
            {
                CXPS_LOG_ERROR(AT_LOC <<
                    "Major version of the cached settings file - " << majorVersion <<
                    " doesn't match the expected major version - " <<
                    CacheDataHeader::CURRENT_CACHED_DATA_MAJOR_VERSION);

                knownToFailHeader = headerStr;
                knownToFailContent = settingsFileContent;
                return PSSettingsPtr();
            }

            if (m_serverOptions->psSettingsEnforceMinorVersionCheck() &&
                minorVersion != CacheDataHeader::CURRENT_CACHED_DATA_MINOR_VERSION)
            {
                CXPS_LOG_ERROR(AT_LOC <<
                    "Minor version of the cached settings file - " << minorVersion <<
                    " doesn't match the expected minor version - " <<
                    CacheDataHeader::CURRENT_CACHED_DATA_MINOR_VERSION);

                knownToFailHeader = headerStr;
                knownToFailContent = settingsFileContent;
                return PSSettingsPtr();
            }
        }

        if (m_serverOptions->psSettingsVerifyHeader() &&
            !parsedHeader.IsMatchingContent(settingsFileContent))
        {
            CXPS_LOG_ERROR(AT_LOC <<
                "Checksum validation failed for the cached settings file - " <<
                parsedHeader.Checksum << " (" << parsedHeader.ChecksumType << ")");

            knownToFailHeader = headerStr;
            knownToFailContent = settingsFileContent;
            return PSSettingsPtr();
        }
    }

    // the contents of the file are good, so update the cached time and content
    m_MTime = t;

    if (m_settingsFileContent == settingsFileContent)
    {
        return PSSettingsPtr();
    }

    PSSettingsPtr parsedSettingsPtr;
    try
    {
        parsedSettingsPtr = boost::make_shared<PSSettings>(
            JSON::consumer<PSSettings>::convert(settingsFileContent, true)); // std::move() candidate

        m_settingsFileContent = settingsFileContent;
    }
    catch (const std::exception &ex)
    {
        CXPS_LOG_ERROR(AT_LOC <<
            "Json serialization failed for the content in the cached settings file - " << ex.what());

        knownToFailHeader = headerStr;
        knownToFailContent = settingsFileContent;
        return PSSettingsPtr();
    }

    // TODO-SanKumar-2002: By setting these values even in +ve case, we could
    // avoid unnecessary JSON parsings as well as callbacks at the caller. Adverse
    // effect would be that if any failed callbacks / other intermittent logic
    // errors wouldn't be retried.
    knownToFailHeader.clear();
    knownToFailContent.clear();
    return parsedSettingsPtr;
}