<commit_before>#include "CppUTest/TestHarness.h"
#include "timelib.h"

TEST_GROUP(timezone_same)
{
	int same;
	timelib_time *t1, *t2;
	timelib_tzinfo *tzi1, *tzi2;
	int dummy_error;

	TEST_SETUP()
	{
		same = 0;
		t1 = t2 = NULL;
		tzi1 = NULL;
		tzi2 = NULL;
	}

	void test_parse(const char *str1, const char *str2)
	{
		t1 = timelib_strtotime(str1, strlen(str1), NULL, timelib_builtin_db(), timelib_parse_tzfile);
		t2 = timelib_strtotime(str2, strlen(str2), NULL, timelib_builtin_db(), timelib_parse_tzfile);

		same = timelib_same_timezone(t1, t2);
	}

	void test_parse_with_tz(const char *str1, const char *str2, const char *tz1, const char *tz2)
	{
		t1 = timelib_strtotime(str1, strlen(str1), NULL, timelib_builtin_db(), timelib_parse_tzfile);
		t2 = timelib_strtotime(str2, strlen(str2), NULL, timelib_builtin_db(), timelib_parse_tzfile);

		if (tz1) {
			tzi1 = timelib_parse_tzfile((char*) tz1, timelib_builtin_db(), &dummy_error);
			timelib_update_ts(t1, tzi1);
		}
		if (tz2) {
			tzi2 = timelib_parse_tzfile((char*) tz2, timelib_builtin_db(), &dummy_error);
			timelib_update_ts(t2, tzi2);
		}

		same = timelib_same_timezone(t1, t2);
	}

	TEST_TEARDOWN()
	{
		if (t1) {
			timelib_time_dtor(t1);
		}
		if (t2) {
			timelib_time_dtor(t2);
		}
		if (tzi1) {
			timelib_tzinfo_dtor(tzi1);
		}
		if (tzi2) {
			timelib_tzinfo_dtor(tzi2);
		}
	}
};

#define CHECK_SAME(s,ty1,ty2) \
	LONGS_EQUAL(s, same); \
	LONGS_EQUAL(ty1, t1->zone_type); \
	LONGS_EQUAL(ty2, t2->zone_type);

TEST(timezone_same, type1_type1_same1)
{
	test_parse("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07 GMT+0100");
	CHECK_SAME(1, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type1_type1_notsame1)
{
	test_parse("2021-11-05 11:23:39 GMT+0200", "2021-11-05 11:24:07 GMT+0100");
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type1_type1_notsame2)
{
	test_parse("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07 GMT+0200");
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type2_type2_same1)
{
	test_parse("2021-11-05 11:23:39 CET", "2021-11-05 11:24:07 CET");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_same2)
{
	test_parse("2021-11-05 11:23:39 BST", "2021-11-05 11:24:07 CET");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_same3)
{
	test_parse("2021-11-05 11:23:39 CDT", "2021-11-05 11:24:07 EST");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_same4)
{
	test_parse("2021-11-05 11:23:39 EST", "2021-11-05 11:24:07 CDT");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_notsame1)
{
	test_parse("2021-11-05 11:23:39 EDT", "2021-11-05 11:24:07 CDT");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_notsame2)
{
	test_parse("2021-11-05 11:23:39 CET", "2021-11-05 11:24:07 CEST");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type3_type3_same1)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07", "Europe/Amsterdam", "Europe/Amsterdam");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type3_type3_notsame1)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07", "Europe/Amsterdam", "America/Chicago");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type1_type2)
{
	test_parse_with_tz("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07 BST", NULL, NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type1_type3)
{
	test_parse_with_tz("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07", NULL, "Europe/Berlin");
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type2_type1)
{
	test_parse_with_tz("2021-11-05 11:23:39 CEST", "2021-11-05 11:24:07 GMT+0200", NULL, NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type2_type3)
{
	test_parse_with_tz("2021-11-05 11:23:39 CET", "2021-11-05 11:24:07", NULL, "Europe/Berlin");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type3_type1)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07 GMT+0200", "Europe/Berlin", NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type3_type2)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07 CET", "Europe/Berlin", NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_ABBR);
}
<commit_msg>Fixed compilation on some platforms<commit_after>#include "CppUTest/TestHarness.h"
#include "timelib.h"
#include <cstring>

TEST_GROUP(timezone_same)
{
	int same;
	timelib_time *t1, *t2;
	timelib_tzinfo *tzi1, *tzi2;
	int dummy_error;

	TEST_SETUP()
	{
		same = 0;
		t1 = t2 = NULL;
		tzi1 = NULL;
		tzi2 = NULL;
	}

	void test_parse(const char *str1, const char *str2)
	{
		t1 = timelib_strtotime(str1, strlen(str1), NULL, timelib_builtin_db(), timelib_parse_tzfile);
		t2 = timelib_strtotime(str2, strlen(str2), NULL, timelib_builtin_db(), timelib_parse_tzfile);

		same = timelib_same_timezone(t1, t2);
	}

	void test_parse_with_tz(const char *str1, const char *str2, const char *tz1, const char *tz2)
	{
		t1 = timelib_strtotime(str1, strlen(str1), NULL, timelib_builtin_db(), timelib_parse_tzfile);
		t2 = timelib_strtotime(str2, strlen(str2), NULL, timelib_builtin_db(), timelib_parse_tzfile);

		if (tz1) {
			tzi1 = timelib_parse_tzfile((char*) tz1, timelib_builtin_db(), &dummy_error);
			timelib_update_ts(t1, tzi1);
		}
		if (tz2) {
			tzi2 = timelib_parse_tzfile((char*) tz2, timelib_builtin_db(), &dummy_error);
			timelib_update_ts(t2, tzi2);
		}

		same = timelib_same_timezone(t1, t2);
	}

	TEST_TEARDOWN()
	{
		if (t1) {
			timelib_time_dtor(t1);
		}
		if (t2) {
			timelib_time_dtor(t2);
		}
		if (tzi1) {
			timelib_tzinfo_dtor(tzi1);
		}
		if (tzi2) {
			timelib_tzinfo_dtor(tzi2);
		}
	}
};

#define CHECK_SAME(s,ty1,ty2) \
	LONGS_EQUAL(s, same); \
	LONGS_EQUAL(ty1, t1->zone_type); \
	LONGS_EQUAL(ty2, t2->zone_type);

TEST(timezone_same, type1_type1_same1)
{
	test_parse("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07 GMT+0100");
	CHECK_SAME(1, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type1_type1_notsame1)
{
	test_parse("2021-11-05 11:23:39 GMT+0200", "2021-11-05 11:24:07 GMT+0100");
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type1_type1_notsame2)
{
	test_parse("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07 GMT+0200");
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type2_type2_same1)
{
	test_parse("2021-11-05 11:23:39 CET", "2021-11-05 11:24:07 CET");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_same2)
{
	test_parse("2021-11-05 11:23:39 BST", "2021-11-05 11:24:07 CET");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_same3)
{
	test_parse("2021-11-05 11:23:39 CDT", "2021-11-05 11:24:07 EST");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_same4)
{
	test_parse("2021-11-05 11:23:39 EST", "2021-11-05 11:24:07 CDT");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_notsame1)
{
	test_parse("2021-11-05 11:23:39 EDT", "2021-11-05 11:24:07 CDT");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type2_type2_notsame2)
{
	test_parse("2021-11-05 11:23:39 CET", "2021-11-05 11:24:07 CEST");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type3_type3_same1)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07", "Europe/Amsterdam", "Europe/Amsterdam");
	CHECK_SAME(1, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type3_type3_notsame1)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07", "Europe/Amsterdam", "America/Chicago");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type1_type2)
{
	test_parse_with_tz("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07 BST", NULL, NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_ABBR);
}

TEST(timezone_same, type1_type3)
{
	test_parse_with_tz("2021-11-05 11:23:39 GMT+0100", "2021-11-05 11:24:07", NULL, "Europe/Berlin");
	CHECK_SAME(0, TIMELIB_ZONETYPE_OFFSET, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type2_type1)
{
	test_parse_with_tz("2021-11-05 11:23:39 CEST", "2021-11-05 11:24:07 GMT+0200", NULL, NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type2_type3)
{
	test_parse_with_tz("2021-11-05 11:23:39 CET", "2021-11-05 11:24:07", NULL, "Europe/Berlin");
	CHECK_SAME(0, TIMELIB_ZONETYPE_ABBR, TIMELIB_ZONETYPE_ID);
}

TEST(timezone_same, type3_type1)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07 GMT+0200", "Europe/Berlin", NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_OFFSET);
}

TEST(timezone_same, type3_type2)
{
	test_parse_with_tz("2021-11-05 11:23:39", "2021-11-05 11:24:07 CET", "Europe/Berlin", NULL);
	CHECK_SAME(0, TIMELIB_ZONETYPE_ID, TIMELIB_ZONETYPE_ABBR);
}
<|endoftext|>


<commit_before>/*
 * This file is part of signon
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include "pluginproxy.h"

#include <sys/types.h>
#include <pwd.h>

#include <QStringList>
#include <QThreadStorage>
#include <QThread>

#include "SignOn/uisessiondata_priv.h"
#include "SignOn/signonplugincommon.h"

/*
 *   TODO: remove the "SignOn/authpluginif.h" include below after the removal
 *         of the deprecated error handling (needed here only for the deprecated
 *         AuthPluginError::PLUGIN_ERROR_GENERAL).
 */
#include "SignOn/authpluginif.h"

using namespace SignOn;

//TODO get this from config
#define REMOTEPLUGIN_BIN_PATH QLatin1String("/usr/bin/signonpluginprocess")
#define PLUGINPROCESS_TIMEOUT 5000

namespace SignonDaemonNS {

    PluginProcess::PluginProcess(QObject *parent) : QProcess(parent)
    {
    }

    PluginProcess::~PluginProcess()
    {
        if (state() != QProcess::NotRunning)
            terminate();

        waitForFinished();
    }

    void PluginProcess::setupChildProcess()
    {
        // Drop all root privileges in the child process and switch to signon user
#ifndef NO_SIGNON_USER
        //get uid and gid
        struct passwd *passwdRecord = getpwnam("signon");
        if ( !passwdRecord ){
            fprintf(stderr, "failed to get user: signon\n");
            emit QProcess::finished(2, QProcess::NormalExit);
            exit(2);
        }
#ifdef SIGNOND_TRACE
        //this is run in remote plugin process, so trace should go to stderr
        fprintf(stderr, "got user: %s with uid: %d\n", passwdRecord->pw_name, passwdRecord->pw_uid);
#endif
        if (( ::setgid(passwdRecord->pw_gid))
                || (::setuid(passwdRecord->pw_uid))
                || (::getuid() != passwdRecord->pw_uid)
                ) {
            fprintf(stderr, "failed to set user: %s with uid: %d", passwdRecord->pw_name, passwdRecord->pw_uid);
            emit QProcess::finished(2, QProcess::NormalExit);
            exit(2);
        }
 #endif
    }

    PluginProxy::PluginProxy(QString type, QObject *parent)
            : QObject(parent)
    {
        TRACE();

        m_type = type;
        m_isProcessing = false;
        m_process = new PluginProcess(this);

        connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(onReadStandardError()));

        /*
         * TODO: some error handling should be added here, at least remove of current
         * request data from the top of the queue and reply an error code
         * */
        connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onExit(int, QProcess::ExitStatus)));
        connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError(QProcess::ProcessError)));
    }

    PluginProxy::~PluginProxy()
    {
    }

    PluginProxy* PluginProxy::createNewPluginProxy(const QString &type)
    {
        PluginProxy *pp = new PluginProxy(type);

        QStringList args = QStringList() << pp->m_type;
        pp->m_process->start(REMOTEPLUGIN_BIN_PATH, args);

        QByteArray tmp;

        if (!pp->waitForStarted(PLUGINPROCESS_TIMEOUT)) {
            TRACE() << "The process cannot be started";
            delete pp;
            return NULL;
        }

        if (!pp->readOnReady(tmp, PLUGINPROCESS_TIMEOUT)) {
            TRACE() << "The process cannot load plugin";
            delete pp;
            return NULL;
        }

        pp->m_type = pp->queryType();
        pp->m_mechanisms = pp->queryMechanisms();

        connect(pp->m_process, SIGNAL(readyReadStandardOutput()), pp, SLOT(onReadStandardOutput()));

        TRACE() << "The process is started";
        return pp;
    }

   bool PluginProxy::process(const QString &cancelKey, const QVariantMap &inData, const QString &mechanism)
   {
        TRACE();
        if (!restartIfRequired())
            return false;

        m_cancelKey = cancelKey;
        QVariant value = inData.value(SSOUI_KEY_UIPOLICY);
        m_uiPolicy = value.toInt();
        //TODO check if this part can be done in SignonSessionCore::startProcess()
        QVariantMap inDataTmp = inData;
        if (m_uiPolicy == RequestPasswordPolicy)
            inDataTmp.remove(SSOUI_KEY_PASSWORD);
        QDataStream in(m_process);

        in << (quint32)PLUGIN_OP_PROCESS;
        in << QVariant(inDataTmp);
        in << QVariant(mechanism);

        m_isProcessing = true;

        return true;
    }

   bool PluginProxy::processUi(const QString &cancelKey, const QVariantMap &inData)
   {
        TRACE() << inData;

        if (!restartIfRequired())
            return false;

        m_cancelKey = cancelKey;

        QDataStream in(m_process);

        in << (quint32)PLUGIN_OP_PROCESS_UI;
        in << QVariant(inData);

        m_isProcessing = true;

        return true;
    }

   bool PluginProxy::processRefresh(const QString &cancelKey, const QVariantMap &inData)
   {
        TRACE() << inData;

        if (!restartIfRequired())
            return false;

        m_cancelKey = cancelKey;

        QDataStream in(m_process);

        in << (quint32)PLUGIN_OP_REFRESH;
        in << QVariant(inData);

        m_isProcessing = true;

        return true;
    }

   void PluginProxy::cancel()
   {
       TRACE();
       QDataStream in(m_process);
       in << (quint32)PLUGIN_OP_CANCEL;
    }

   void PluginProxy::stop()
   {
       TRACE();
       QDataStream in(m_process);
       in << (quint32)PLUGIN_OP_STOP;
    }

    bool PluginProxy::readOnReady(QByteArray &buffer, int timeout)
    {
        bool ready = m_process->waitForReadyRead(timeout);

        if (ready) {
            if (!m_process->bytesAvailable())
                return false;

            while (m_process->bytesAvailable())
                buffer += m_process->readAllStandardOutput();
        }

        return ready;
    }

    bool PluginProxy::isProcessing()
    {
        return m_isProcessing;
    }

    void PluginProxy::onReadStandardOutput()
    {
        disconnect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadStandardOutput()));

        QByteArray token;

        QVariant infoVa;
        QVariantMap info;

        if (!m_process->bytesAvailable()) {
            qCritical() << "No information available on process";
            m_isProcessing = false;
            emit processError(m_cancelKey, Error::InternalServer, QString());
            return;
        }

        QByteArray buffer;

        while (m_process->bytesAvailable())
            buffer += m_process->readAllStandardOutput();

        /*
         * we need to analyze the whole buffer
         * and if it contains error then emit only error
         * otherwise process/emit the incoming information
         * one by one
         * */
        QDataStream out(buffer);
        bool isResultObtained = false;

        while (out.status() == QDataStream::Ok) {
            quint32 opres;
            out >> opres; //result of operation: error code

            TRACE() << opres;

            if (opres == PLUGIN_RESPONSE_RESULT) {
                TRACE() << "PLUGIN_RESPONSE_RESULT";
                out >> infoVa; //SessionData in QVariant
                info = infoVa.toMap();
                m_isProcessing = false;

                if (!isResultObtained)
                    emit processResultReply(m_cancelKey, info);
                else
                    BLAME() << "Unexpected plugin response: " << info;

                isResultObtained = true;
            } else if (opres == PLUGIN_RESPONSE_STORE) {
                TRACE() << "PLUGIN_RESPONSE_STORE";
                out >> infoVa; //SessionData in QVariant
                info = infoVa.toMap();

                if (!isResultObtained)
                    emit processStore(m_cancelKey, info);
                else
                    BLAME() << "Unexpected plugin store: " << info;

            } else if (opres == PLUGIN_RESPONSE_UI) {
                TRACE() << "PLUGIN_RESPONSE_UI";
                out >> infoVa; //UiSessionData in QVariant
                info = infoVa.toMap();

                if (!isResultObtained) {
                    bool allowed = true;

                    if (m_uiPolicy == NoUserInteractionPolicy)
                        allowed = false;

                    if (m_uiPolicy == ValidationPolicy) {
                        bool credentialsQueried =
                            (info.contains(SSOUI_KEY_QUERYUSERNAME)
                            || info.contains(SSOUI_KEY_QUERYPASSWORD));

                        bool captchaQueried  =
                            (info.contains(SSOUI_KEY_CAPTCHAIMG)
                             || info.contains(SSOUI_KEY_CAPTCHAURL));

                        if (credentialsQueried && !captchaQueried)
                            allowed = false;
                    }

                    if (!allowed) {
                        //set error and return;
                        TRACE() << "ui policy prevented ui launch";
                        info.insert(SSOUI_KEY_ERROR, QUERY_ERROR_FORBIDDEN);
                        processUi(m_cancelKey, info);
                    } else {
                        TRACE() << "open ui";
                        emit processUiRequest(m_cancelKey, info);
                    }
                } else {
                    BLAME() << "Unexpected plugin ui response: " << info;
                }
            } else if (opres == PLUGIN_RESPONSE_REFRESHED) {
                TRACE() << "PLUGIN_RESPONSE_REFRESHED";
                out >> infoVa; //UiSessionData in QVariant
                info = infoVa.toMap();

                if (!isResultObtained)
                    emit processRefreshRequest(m_cancelKey, info);
                else
                    BLAME() << "Unexpected plugin ui response: " << info;
            } else if (opres == PLUGIN_RESPONSE_ERROR) {
                TRACE() << "PLUGIN_RESPONSE_ERROR";
                quint32 err;
                QString errorMessage;
                out >> err;
                out >> errorMessage;
                m_isProcessing = false;

                if (!isResultObtained)
                    emit processError(m_cancelKey, (int)err, errorMessage);
                else
                    BLAME() << "Unexpected plugin error: " << errorMessage;

                isResultObtained = true;
            } else if (opres == PLUGIN_RESPONSE_SIGNAL) {
                TRACE() << "PLUGIN_RESPONSE_SIGNAL";
                quint32 state;
                QString message;

                out >> state;
                out >> message;

                if (!isResultObtained)
                    emit stateChanged(m_cancelKey, (int)state, message);
                else
                    BLAME() << "Unexpected plugin signal: " << state << " " << message;
            }
        }

        connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadStandardOutput()));
    }

    void PluginProxy::onReadStandardError()
    {
        QString ba = QString::fromLatin1(m_process->readAllStandardError());
        TRACE() << ba;
    }

    void PluginProxy::onExit(int exitCode, QProcess::ExitStatus exitStatus)
    {
        TRACE() << "Plugin process exit with code " << exitCode << " : " << exitStatus;

        if (m_isProcessing || exitStatus == QProcess::CrashExit) {
            qCritical() << "Challenge produces CRASH!";
            emit processError(m_cancelKey, Error::InternalServer, QLatin1String("plugin processed crashed"));
        }
        if (exitCode == 2) {
            TRACE() << "plugin process terminated because cannot change user";
        }

        m_isProcessing = false;
    }

    void PluginProxy::onError(QProcess::ProcessError err)
    {
        TRACE() << "Error: " << err;
    }

    QString PluginProxy::queryType()
    {
        TRACE();

        if (!restartIfRequired())
            return QString();

        QDataStream ds(m_process);
        ds << (quint32)PLUGIN_OP_TYPE;

        QByteArray typeBa, buffer;
        bool result;

        if ((result = readOnReady(buffer, PLUGINPROCESS_TIMEOUT))) {
            QDataStream out(buffer);
            out >> typeBa;
        } else
            qCritical("PluginProxy returned NULL result");

        return QString::fromLatin1(typeBa);
    }

    QStringList PluginProxy::queryMechanisms()
    {
        TRACE();

        if (!restartIfRequired())
            return QStringList();

        QDataStream in(m_process);
        in << (quint32)PLUGIN_OP_MECHANISMS;

        QByteArray buffer;
        QStringList strList;
        bool result;

        if ((result = readOnReady(buffer, PLUGINPROCESS_TIMEOUT))) {
            QVariant mechanismsVar;
            QDataStream out(buffer);

            out >> mechanismsVar;
            QVariantList varList = mechanismsVar.toList();

            for (int i = 0; i < varList.count(); i++)
                    strList << varList.at(i).toString();

            TRACE() << strList;
        } else
            qCritical("PluginProxy returned NULL result");

        return strList;
    }

    bool PluginProxy::waitForStarted(int timeout)
    {
        return m_process->waitForStarted(timeout);
    }

    bool PluginProxy::waitForFinished(int timeout)
    {
        return m_process->waitForFinished(timeout);
    }

    bool PluginProxy::restartIfRequired()
    {
        if (m_process->state() == QProcess::NotRunning) {
            TRACE() << "RESTART REQUIRED";
            m_process->start(REMOTEPLUGIN_BIN_PATH, QStringList(m_type));

            QByteArray tmp;
            if (!waitForStarted(PLUGINPROCESS_TIMEOUT) || !readOnReady(tmp, PLUGINPROCESS_TIMEOUT))
                return false;
        }
        return true;
    }

} //namespace SignonDaemonNS
<commit_msg>Fix for signond unresponsiveness after closing an authentication session.<commit_after>/*
 * This file is part of signon
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include "pluginproxy.h"

#include <sys/types.h>
#include <pwd.h>

#include <QStringList>
#include <QThreadStorage>
#include <QThread>

#include "SignOn/uisessiondata_priv.h"
#include "SignOn/signonplugincommon.h"

/*
 *   TODO: remove the "SignOn/authpluginif.h" include below after the removal
 *         of the deprecated error handling (needed here only for the deprecated
 *         AuthPluginError::PLUGIN_ERROR_GENERAL).
 */
#include "SignOn/authpluginif.h"

using namespace SignOn;

//TODO get this from config
#define REMOTEPLUGIN_BIN_PATH QLatin1String("/usr/bin/signonpluginprocess")
#define PLUGINPROCESS_TIMEOUT 5000

namespace SignonDaemonNS {

    PluginProcess::PluginProcess(QObject *parent) : QProcess(parent)
    {
    }

    PluginProcess::~PluginProcess()
    {
    }

    void PluginProcess::setupChildProcess()
    {
        // Drop all root privileges in the child process and switch to signon user
#ifndef NO_SIGNON_USER
        //get uid and gid
        struct passwd *passwdRecord = getpwnam("signon");
        if ( !passwdRecord ){
            fprintf(stderr, "failed to get user: signon\n");
            emit QProcess::finished(2, QProcess::NormalExit);
            exit(2);
        }
#ifdef SIGNOND_TRACE
        //this is run in remote plugin process, so trace should go to stderr
        fprintf(stderr, "got user: %s with uid: %d\n", passwdRecord->pw_name, passwdRecord->pw_uid);
#endif
        if (( ::setgid(passwdRecord->pw_gid))
                || (::setuid(passwdRecord->pw_uid))
                || (::getuid() != passwdRecord->pw_uid)
                ) {
            fprintf(stderr, "failed to set user: %s with uid: %d", passwdRecord->pw_name, passwdRecord->pw_uid);
            emit QProcess::finished(2, QProcess::NormalExit);
            exit(2);
        }
 #endif
    }

    PluginProxy::PluginProxy(QString type, QObject *parent)
            : QObject(parent)
    {
        TRACE();

        m_type = type;
        m_isProcessing = false;
        m_process = new PluginProcess(this);

        connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(onReadStandardError()));

        /*
         * TODO: some error handling should be added here, at least remove of current
         * request data from the top of the queue and reply an error code
         * */
        connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onExit(int, QProcess::ExitStatus)));
        connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError(QProcess::ProcessError)));
    }

    PluginProxy::~PluginProxy()
    {
        m_process->close();
        m_process->waitForFinished();
    }

    PluginProxy* PluginProxy::createNewPluginProxy(const QString &type)
    {
        PluginProxy *pp = new PluginProxy(type);

        QStringList args = QStringList() << pp->m_type;
        pp->m_process->start(REMOTEPLUGIN_BIN_PATH, args);

        QByteArray tmp;

        if (!pp->waitForStarted(PLUGINPROCESS_TIMEOUT)) {
            TRACE() << "The process cannot be started";
            delete pp;
            return NULL;
        }

        if (!pp->readOnReady(tmp, PLUGINPROCESS_TIMEOUT)) {
            TRACE() << "The process cannot load plugin";
            delete pp;
            return NULL;
        }

        pp->m_type = pp->queryType();
        pp->m_mechanisms = pp->queryMechanisms();

        connect(pp->m_process, SIGNAL(readyReadStandardOutput()), pp, SLOT(onReadStandardOutput()));

        TRACE() << "The process is started";
        return pp;
    }

   bool PluginProxy::process(const QString &cancelKey, const QVariantMap &inData, const QString &mechanism)
   {
        TRACE();
        if (!restartIfRequired())
            return false;

        m_cancelKey = cancelKey;
        QVariant value = inData.value(SSOUI_KEY_UIPOLICY);
        m_uiPolicy = value.toInt();
        //TODO check if this part can be done in SignonSessionCore::startProcess()
        QVariantMap inDataTmp = inData;
        if (m_uiPolicy == RequestPasswordPolicy)
            inDataTmp.remove(SSOUI_KEY_PASSWORD);
        QDataStream in(m_process);

        in << (quint32)PLUGIN_OP_PROCESS;
        in << QVariant(inDataTmp);
        in << QVariant(mechanism);

        m_isProcessing = true;

        return true;
    }

   bool PluginProxy::processUi(const QString &cancelKey, const QVariantMap &inData)
   {
        TRACE() << inData;

        if (!restartIfRequired())
            return false;

        m_cancelKey = cancelKey;

        QDataStream in(m_process);

        in << (quint32)PLUGIN_OP_PROCESS_UI;
        in << QVariant(inData);

        m_isProcessing = true;

        return true;
    }

   bool PluginProxy::processRefresh(const QString &cancelKey, const QVariantMap &inData)
   {
        TRACE() << inData;

        if (!restartIfRequired())
            return false;

        m_cancelKey = cancelKey;

        QDataStream in(m_process);

        in << (quint32)PLUGIN_OP_REFRESH;
        in << QVariant(inData);

        m_isProcessing = true;

        return true;
    }

   void PluginProxy::cancel()
   {
       TRACE();
       QDataStream in(m_process);
       in << (quint32)PLUGIN_OP_CANCEL;
    }

   void PluginProxy::stop()
   {
       TRACE();
       QDataStream in(m_process);
       in << (quint32)PLUGIN_OP_STOP;
    }

    bool PluginProxy::readOnReady(QByteArray &buffer, int timeout)
    {
        bool ready = m_process->waitForReadyRead(timeout);

        if (ready) {
            if (!m_process->bytesAvailable())
                return false;

            while (m_process->bytesAvailable())
                buffer += m_process->readAllStandardOutput();
        }

        return ready;
    }

    bool PluginProxy::isProcessing()
    {
        return m_isProcessing;
    }

    void PluginProxy::onReadStandardOutput()
    {
        disconnect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadStandardOutput()));

        QByteArray token;

        QVariant infoVa;
        QVariantMap info;

        if (!m_process->bytesAvailable()) {
            qCritical() << "No information available on process";
            m_isProcessing = false;
            emit processError(m_cancelKey, Error::InternalServer, QString());
            return;
        }

        QByteArray buffer;

        while (m_process->bytesAvailable())
            buffer += m_process->readAllStandardOutput();

        /*
         * we need to analyze the whole buffer
         * and if it contains error then emit only error
         * otherwise process/emit the incoming information
         * one by one
         * */
        QDataStream out(buffer);
        bool isResultObtained = false;

        while (out.status() == QDataStream::Ok) {
            quint32 opres;
            out >> opres; //result of operation: error code

            TRACE() << opres;

            if (opres == PLUGIN_RESPONSE_RESULT) {
                TRACE() << "PLUGIN_RESPONSE_RESULT";
                out >> infoVa; //SessionData in QVariant
                info = infoVa.toMap();
                m_isProcessing = false;

                if (!isResultObtained)
                    emit processResultReply(m_cancelKey, info);
                else
                    BLAME() << "Unexpected plugin response: " << info;

                isResultObtained = true;
            } else if (opres == PLUGIN_RESPONSE_STORE) {
                TRACE() << "PLUGIN_RESPONSE_STORE";
                out >> infoVa; //SessionData in QVariant
                info = infoVa.toMap();

                if (!isResultObtained)
                    emit processStore(m_cancelKey, info);
                else
                    BLAME() << "Unexpected plugin store: " << info;

            } else if (opres == PLUGIN_RESPONSE_UI) {
                TRACE() << "PLUGIN_RESPONSE_UI";
                out >> infoVa; //UiSessionData in QVariant
                info = infoVa.toMap();

                if (!isResultObtained) {
                    bool allowed = true;

                    if (m_uiPolicy == NoUserInteractionPolicy)
                        allowed = false;

                    if (m_uiPolicy == ValidationPolicy) {
                        bool credentialsQueried =
                            (info.contains(SSOUI_KEY_QUERYUSERNAME)
                            || info.contains(SSOUI_KEY_QUERYPASSWORD));

                        bool captchaQueried  =
                            (info.contains(SSOUI_KEY_CAPTCHAIMG)
                             || info.contains(SSOUI_KEY_CAPTCHAURL));

                        if (credentialsQueried && !captchaQueried)
                            allowed = false;
                    }

                    if (!allowed) {
                        //set error and return;
                        TRACE() << "ui policy prevented ui launch";
                        info.insert(SSOUI_KEY_ERROR, QUERY_ERROR_FORBIDDEN);
                        processUi(m_cancelKey, info);
                    } else {
                        TRACE() << "open ui";
                        emit processUiRequest(m_cancelKey, info);
                    }
                } else {
                    BLAME() << "Unexpected plugin ui response: " << info;
                }
            } else if (opres == PLUGIN_RESPONSE_REFRESHED) {
                TRACE() << "PLUGIN_RESPONSE_REFRESHED";
                out >> infoVa; //UiSessionData in QVariant
                info = infoVa.toMap();

                if (!isResultObtained)
                    emit processRefreshRequest(m_cancelKey, info);
                else
                    BLAME() << "Unexpected plugin ui response: " << info;
            } else if (opres == PLUGIN_RESPONSE_ERROR) {
                TRACE() << "PLUGIN_RESPONSE_ERROR";
                quint32 err;
                QString errorMessage;
                out >> err;
                out >> errorMessage;
                m_isProcessing = false;

                if (!isResultObtained)
                    emit processError(m_cancelKey, (int)err, errorMessage);
                else
                    BLAME() << "Unexpected plugin error: " << errorMessage;

                isResultObtained = true;
            } else if (opres == PLUGIN_RESPONSE_SIGNAL) {
                TRACE() << "PLUGIN_RESPONSE_SIGNAL";
                quint32 state;
                QString message;

                out >> state;
                out >> message;

                if (!isResultObtained)
                    emit stateChanged(m_cancelKey, (int)state, message);
                else
                    BLAME() << "Unexpected plugin signal: " << state << " " << message;
            }
        }

        connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadStandardOutput()));
    }

    void PluginProxy::onReadStandardError()
    {
        QString ba = QString::fromLatin1(m_process->readAllStandardError());
        TRACE() << ba;
    }

    void PluginProxy::onExit(int exitCode, QProcess::ExitStatus exitStatus)
    {
        TRACE() << "Plugin process exit with code " << exitCode << " : " << exitStatus;

        if (m_isProcessing || exitStatus == QProcess::CrashExit) {
            qCritical() << "Challenge produces CRASH!";
            emit processError(m_cancelKey, Error::InternalServer, QLatin1String("plugin processed crashed"));
        }
        if (exitCode == 2) {
            TRACE() << "plugin process terminated because cannot change user";
        }

        m_isProcessing = false;
    }

    void PluginProxy::onError(QProcess::ProcessError err)
    {
        TRACE() << "Error: " << err;
    }

    QString PluginProxy::queryType()
    {
        TRACE();

        if (!restartIfRequired())
            return QString();

        QDataStream ds(m_process);
        ds << (quint32)PLUGIN_OP_TYPE;

        QByteArray typeBa, buffer;
        bool result;

        if ((result = readOnReady(buffer, PLUGINPROCESS_TIMEOUT))) {
            QDataStream out(buffer);
            out >> typeBa;
        } else
            qCritical("PluginProxy returned NULL result");

        return QString::fromLatin1(typeBa);
    }

    QStringList PluginProxy::queryMechanisms()
    {
        TRACE();

        if (!restartIfRequired())
            return QStringList();

        QDataStream in(m_process);
        in << (quint32)PLUGIN_OP_MECHANISMS;

        QByteArray buffer;
        QStringList strList;
        bool result;

        if ((result = readOnReady(buffer, PLUGINPROCESS_TIMEOUT))) {
            QVariant mechanismsVar;
            QDataStream out(buffer);

            out >> mechanismsVar;
            QVariantList varList = mechanismsVar.toList();

            for (int i = 0; i < varList.count(); i++)
                    strList << varList.at(i).toString();

            TRACE() << strList;
        } else
            qCritical("PluginProxy returned NULL result");

        return strList;
    }

    bool PluginProxy::waitForStarted(int timeout)
    {
        return m_process->waitForStarted(timeout);
    }

    bool PluginProxy::waitForFinished(int timeout)
    {
        return m_process->waitForFinished(timeout);
    }

    bool PluginProxy::restartIfRequired()
    {
        if (m_process->state() == QProcess::NotRunning) {
            TRACE() << "RESTART REQUIRED";
            m_process->start(REMOTEPLUGIN_BIN_PATH, QStringList(m_type));

            QByteArray tmp;
            if (!waitForStarted(PLUGINPROCESS_TIMEOUT) || !readOnReady(tmp, PLUGINPROCESS_TIMEOUT))
                return false;
        }
        return true;
    }

} //namespace SignonDaemonNS
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016 DeepCortex GmbH <legal@eventql.io>
 * Authors:
 *   - Paul Asmuth <paul@eventql.io>
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License ("the license") as
 * published by the Free Software Foundation, either version 3 of the License,
 * or any later version.
 *
 * In accordance with Section 7(e) of the license, the licensing of the Program
 * under the license does not imply a trademark license. Therefore any rights,
 * title and interest in our trademarks remain entirely with us.
 *
 * This program 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 license for more details.
 *
 * You can be released from the requirements of the license by purchasing a
 * commercial license. Buying such a license is mandatory as soon as you develop
 * commercial activities involving this program without disclosing the source
 * code of your own applications
 */
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <regex>
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/file.h>
#include <curl/curl.h>
#include <evcollect/util/flagparser.h>
#include <evcollect/util/logging.h>
#include <evcollect/evcollect.h>
#include <evcollect/plugin.h>
#include <evcollect/service.h>
#include <evcollect/config.h>
#include <evcollect/logfile.h>

using namespace evcollect;

void shutdown(int);
ReturnCode daemonize();

static std::unique_ptr<Service> service;

int main(int argc, const char** argv) {
  signal(SIGTERM, shutdown);
  signal(SIGINT, shutdown);
  signal(SIGHUP, shutdown);
  signal(SIGPIPE, SIG_IGN);

  FlagParser flags;

  flags.defineFlag(
      "help",
      FlagParser::T_SWITCH,
      false,
      "?",
      NULL);

  flags.defineFlag(
      "version",
      FlagParser::T_SWITCH,
      false,
      "V",
      NULL);

  flags.defineFlag(
      "spool_dir",
      ::FlagParser::T_STRING,
      false,
      "s",
      NULL);

  flags.defineFlag(
      "config",
      ::FlagParser::T_STRING,
      false,
      "c",
      NULL);

  flags.defineFlag(
      "plugin",
      ::FlagParser::T_STRING,
      false,
      "p",
      NULL);

  flags.defineFlag(
      "plugin_path",
      ::FlagParser::T_STRING,
      false,
      "P",
      "/usr/local/lib/evcollect/plugins");

  flags.defineFlag(
      "loglevel",
      FlagParser::T_STRING,
      false,
      NULL,
      "INFO");

  flags.defineFlag(
      "daemonize",
      FlagParser::T_SWITCH,
      false,
      NULL,
      NULL);

  flags.defineFlag(
      "pidfile",
      FlagParser::T_STRING,
      false,
      NULL,
      NULL);

  flags.defineFlag(
      "log_to_syslog",
      FlagParser::T_SWITCH,
      false,
      NULL,
      NULL);

  flags.defineFlag(
      "nolog_to_stderr",
      FlagParser::T_SWITCH,
      false,
      NULL,
      NULL);

  /* parse flags */
  {
    auto rc = flags.parseArgv(argc, argv);
    if (!rc.isSuccess()) {
      logFatal(rc.getMessage());
      return 1;
    }
  }

  /* setup logging */
  if (!flags.isSet("nolog_to_stderr") && !flags.isSet("daemonize")) {
    Logger::logToStderr("evcollectd");
  }

  if (flags.isSet("log_to_syslog")) {
    Logger::logToSyslog("evcollectd");
  }

  Logger::get()->setMinimumLogLevel(
      strToLogLevel(flags.getString("loglevel")));

  /* print help */
  if (flags.isSet("help") || flags.isSet("version")) {
    std::cerr <<
        StringUtil::format(
            "evcollectd $0\n"
            "Copyright (c) 2016, DeepCortex GmbH. All rights reserved.\n\n",
            EVCOLLECT_VERSION);
  }

  if (flags.isSet("version")) {
    return 0;
  }

  if (flags.isSet("help")) {
    std::cerr <<
        "Usage: $ evcollectd [OPTIONS]\n\n"
        "   -s, --spool_dir <dir>     Where to store temporary files\n"
        "   -c, --config <file>       Load config from file\n"
        "   -p, --plugin <path>       Load a plugin (.so)\n"
        "   -P, --plugin_path <dir>   Set the plugin search path\n"
        "   --daemonize               Daemonize the server\n"
        "   --pidfile <file>          Write a PID file\n"
        "   --loglevel <level>        Minimum log level (default: INFO)\n"
        "   --[no]log_to_syslog       Do[n't] log to syslog\n"
        "   --[no]log_to_stderr       Do[n't] log to stderr\n"
        "   -?, --help                Display this help text and exit\n"
        "   -v, --version             Display the version of this binary and exit\n"
        "                                                       \n"
        "Examples:                                              \n"
        "   $ evcollectd --daemonize --config /etc/evcollect.conf\n";

    return 0;
  }

  /* init libraries */
  curl_global_init(CURL_GLOBAL_DEFAULT);

  /* load config */
  ProcessConfig conf;
  conf.plugin_dir = flags.getString("plugin_path");

  {
    auto config_path = flags.getString("config");
    if (config_path.empty()) {
      logFatal("error: --config is required");
      return 1;
    }

    auto rc = loadConfig(config_path, &conf);
    if (!rc.isSuccess()) {
      logFatal("invalid config: $0", rc.getMessage());
      return 1;
    }
  }

  if (flags.isSet("spool_dir")) {
    conf.spool_dir = flags.getString("spool_dir");
  }

  if (conf.spool_dir.empty()) {
    logFatal("error: --spool_dir is required");
    return 1;
  }

  for (const auto& plugin_path : flags.getStrings("plugin")) {
    conf.load_plugins.push_back(plugin_path);
  }

  /* setup service */
  auto rc = ReturnCode::success();
  service = Service::createService(conf.spool_dir, conf.plugin_dir);

  for (const auto& plugin : conf.load_plugins) {
    if (!rc.isSuccess()) {
      break;
    }

    rc = service->loadPlugin(plugin);
  }

  for (const auto& binding : conf.event_bindings) {
    if (!rc.isSuccess()) {
      break;
    }

    rc = service->addEvent(&binding);
  }

  for (const auto& binding : conf.target_bindings) {
    if (!rc.isSuccess()) {
      break;
    }

    rc = service->addTarget(&binding);
  }

  /* daemonize */
  if (rc.isSuccess() && flags.isSet("daemonize")) {
    rc = daemonize();
  }

  /* write pidfile */
  int pidfile_fd = -1;
  auto pidfile_path = flags.getString("pidfile");
  if (rc.isSuccess() && !pidfile_path.empty()) {
    pidfile_fd = open(
        pidfile_path.c_str(),
        O_WRONLY | O_CREAT,
        0666);

    if (pidfile_fd < 0) {
      rc = ReturnCode::error(
          "IO_ERROR",
          "writing pidfile failed: %s",
          std::strerror(errno));
    }

    if (rc.isSuccess() && flock(pidfile_fd, LOCK_EX | LOCK_NB) != 0) {
      close(pidfile_fd);
      pidfile_fd = -1;
      rc = ReturnCode::error("IO_ERROR", "locking pidfile failed");
    }

    if (rc.isSuccess() && ftruncate(pidfile_fd, 0) != 0) {
      close(pidfile_fd);
      pidfile_fd = -1;
      rc = ReturnCode::error("IO_ERROR", "writing pidfile failed");
    }

    auto pid = StringUtil::toString(getpid());
    if (rc.isSuccess() &&
        write(pidfile_fd, pid.data(), pid.size()) != pid.size()) {
      close(pidfile_fd);
      pidfile_fd = -1;
      rc = ReturnCode::error("IO_ERROR", "writing pidfile failed");
    }
  }

  /* main service loop */
  if (rc.isSuccess()) {
    logInfo("Starting...");
    rc = service->run();
  }

  if (!rc.isSuccess()) {
    logFatal("error: $0", rc.getMessage());
  }

  /* shutdown */
  logInfo("Exiting...");
  signal(SIGTERM, SIG_IGN);
  signal(SIGINT, SIG_IGN);
  signal(SIGHUP, SIG_IGN);
  service.reset(nullptr);

  /* unlock pidfile */
  if (pidfile_fd > 0) {
    unlink(pidfile_path.c_str());
    flock(pidfile_fd, LOCK_UN);
    close(pidfile_fd);
  }

  /* shutdown libraries*/
  curl_global_cleanup();

  return rc.isSuccess() ? 0 : 1;
}

void shutdown(int) {
  if (service) {
    service->kill();
  }
}

ReturnCode daemonize() {
#if defined(_WIN32)
#error "Application::daemonize() not yet implemented for windows"

#elif defined(__APPLE__) && defined(__MACH__)
  // FIXME: deprecated on OSX
  if (::daemon(true /*no chdir*/, true /*no close*/) < 0) {
    return ReturnCode::error("INIT_FAIL", "daemon() failed");
  }

#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
  if (::daemon(true /*no chdir*/, true /*no close*/) < 0) {
    return ReturnCode::error("INIT_FAIL", "daemon() failed");
  }

#else
#error Unsupported OS
#endif
  return ReturnCode::success();
}

<commit_msg>remove extraneous includes<commit_after>/**
 * Copyright (c) 2016 DeepCortex GmbH <legal@eventql.io>
 * Authors:
 *   - Paul Asmuth <paul@eventql.io>
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License ("the license") as
 * published by the Free Software Foundation, either version 3 of the License,
 * or any later version.
 *
 * In accordance with Section 7(e) of the license, the licensing of the Program
 * under the license does not imply a trademark license. Therefore any rights,
 * title and interest in our trademarks remain entirely with us.
 *
 * This program 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 license for more details.
 *
 * You can be released from the requirements of the license by purchasing a
 * commercial license. Buying such a license is mandatory as soon as you develop
 * commercial activities involving this program without disclosing the source
 * code of your own applications
 */
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <regex>
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/file.h>
#include <curl/curl.h>
#include <evcollect/evcollect.h>
#include <evcollect/service.h>
#include <evcollect/util/flagparser.h>
#include <evcollect/util/logging.h>

using namespace evcollect;

void shutdown(int);
ReturnCode daemonize();

static std::unique_ptr<Service> service;

int main(int argc, const char** argv) {
  signal(SIGTERM, shutdown);
  signal(SIGINT, shutdown);
  signal(SIGHUP, shutdown);
  signal(SIGPIPE, SIG_IGN);

  FlagParser flags;

  flags.defineFlag(
      "help",
      FlagParser::T_SWITCH,
      false,
      "?",
      NULL);

  flags.defineFlag(
      "version",
      FlagParser::T_SWITCH,
      false,
      "V",
      NULL);

  flags.defineFlag(
      "spool_dir",
      ::FlagParser::T_STRING,
      false,
      "s",
      NULL);

  flags.defineFlag(
      "config",
      ::FlagParser::T_STRING,
      false,
      "c",
      NULL);

  flags.defineFlag(
      "plugin",
      ::FlagParser::T_STRING,
      false,
      "p",
      NULL);

  flags.defineFlag(
      "plugin_path",
      ::FlagParser::T_STRING,
      false,
      "P",
      "/usr/local/lib/evcollect/plugins");

  flags.defineFlag(
      "loglevel",
      FlagParser::T_STRING,
      false,
      NULL,
      "INFO");

  flags.defineFlag(
      "daemonize",
      FlagParser::T_SWITCH,
      false,
      NULL,
      NULL);

  flags.defineFlag(
      "pidfile",
      FlagParser::T_STRING,
      false,
      NULL,
      NULL);

  flags.defineFlag(
      "log_to_syslog",
      FlagParser::T_SWITCH,
      false,
      NULL,
      NULL);

  flags.defineFlag(
      "nolog_to_stderr",
      FlagParser::T_SWITCH,
      false,
      NULL,
      NULL);

  /* parse flags */
  {
    auto rc = flags.parseArgv(argc, argv);
    if (!rc.isSuccess()) {
      logFatal(rc.getMessage());
      return 1;
    }
  }

  /* setup logging */
  if (!flags.isSet("nolog_to_stderr") && !flags.isSet("daemonize")) {
    Logger::logToStderr("evcollectd");
  }

  if (flags.isSet("log_to_syslog")) {
    Logger::logToSyslog("evcollectd");
  }

  Logger::get()->setMinimumLogLevel(
      strToLogLevel(flags.getString("loglevel")));

  /* print help */
  if (flags.isSet("help") || flags.isSet("version")) {
    std::cerr <<
        StringUtil::format(
            "evcollectd $0\n"
            "Copyright (c) 2016, DeepCortex GmbH. All rights reserved.\n\n",
            EVCOLLECT_VERSION);
  }

  if (flags.isSet("version")) {
    return 0;
  }

  if (flags.isSet("help")) {
    std::cerr <<
        "Usage: $ evcollectd [OPTIONS]\n\n"
        "   -s, --spool_dir <dir>     Where to store temporary files\n"
        "   -c, --config <file>       Load config from file\n"
        "   -p, --plugin <path>       Load a plugin (.so)\n"
        "   -P, --plugin_path <dir>   Set the plugin search path\n"
        "   --daemonize               Daemonize the server\n"
        "   --pidfile <file>          Write a PID file\n"
        "   --loglevel <level>        Minimum log level (default: INFO)\n"
        "   --[no]log_to_syslog       Do[n't] log to syslog\n"
        "   --[no]log_to_stderr       Do[n't] log to stderr\n"
        "   -?, --help                Display this help text and exit\n"
        "   -v, --version             Display the version of this binary and exit\n"
        "                                                       \n"
        "Examples:                                              \n"
        "   $ evcollectd --daemonize --config /etc/evcollect.conf\n";

    return 0;
  }

  /* init libraries */
  curl_global_init(CURL_GLOBAL_DEFAULT);

  /* load config */
  ProcessConfig conf;
  conf.plugin_dir = flags.getString("plugin_path");

  {
    auto config_path = flags.getString("config");
    if (config_path.empty()) {
      logFatal("error: --config is required");
      return 1;
    }

    auto rc = loadConfig(config_path, &conf);
    if (!rc.isSuccess()) {
      logFatal("invalid config: $0", rc.getMessage());
      return 1;
    }
  }

  if (flags.isSet("spool_dir")) {
    conf.spool_dir = flags.getString("spool_dir");
  }

  if (conf.spool_dir.empty()) {
    logFatal("error: --spool_dir is required");
    return 1;
  }

  for (const auto& plugin_path : flags.getStrings("plugin")) {
    conf.load_plugins.push_back(plugin_path);
  }

  /* setup service */
  auto rc = ReturnCode::success();
  service = Service::createService(conf.spool_dir, conf.plugin_dir);

  for (const auto& plugin : conf.load_plugins) {
    if (!rc.isSuccess()) {
      break;
    }

    rc = service->loadPlugin(plugin);
  }

  for (const auto& binding : conf.event_bindings) {
    if (!rc.isSuccess()) {
      break;
    }

    rc = service->addEvent(&binding);
  }

  for (const auto& binding : conf.target_bindings) {
    if (!rc.isSuccess()) {
      break;
    }

    rc = service->addTarget(&binding);
  }

  /* daemonize */
  if (rc.isSuccess() && flags.isSet("daemonize")) {
    rc = daemonize();
  }

  /* write pidfile */
  int pidfile_fd = -1;
  auto pidfile_path = flags.getString("pidfile");
  if (rc.isSuccess() && !pidfile_path.empty()) {
    pidfile_fd = open(
        pidfile_path.c_str(),
        O_WRONLY | O_CREAT,
        0666);

    if (pidfile_fd < 0) {
      rc = ReturnCode::error(
          "IO_ERROR",
          "writing pidfile failed: %s",
          std::strerror(errno));
    }

    if (rc.isSuccess() && flock(pidfile_fd, LOCK_EX | LOCK_NB) != 0) {
      close(pidfile_fd);
      pidfile_fd = -1;
      rc = ReturnCode::error("IO_ERROR", "locking pidfile failed");
    }

    if (rc.isSuccess() && ftruncate(pidfile_fd, 0) != 0) {
      close(pidfile_fd);
      pidfile_fd = -1;
      rc = ReturnCode::error("IO_ERROR", "writing pidfile failed");
    }

    auto pid = StringUtil::toString(getpid());
    if (rc.isSuccess() &&
        write(pidfile_fd, pid.data(), pid.size()) != pid.size()) {
      close(pidfile_fd);
      pidfile_fd = -1;
      rc = ReturnCode::error("IO_ERROR", "writing pidfile failed");
    }
  }

  /* main service loop */
  if (rc.isSuccess()) {
    logInfo("Starting...");
    rc = service->run();
  }

  if (!rc.isSuccess()) {
    logFatal("error: $0", rc.getMessage());
  }

  /* shutdown */
  logInfo("Exiting...");
  signal(SIGTERM, SIG_IGN);
  signal(SIGINT, SIG_IGN);
  signal(SIGHUP, SIG_IGN);
  service.reset(nullptr);

  /* unlock pidfile */
  if (pidfile_fd > 0) {
    unlink(pidfile_path.c_str());
    flock(pidfile_fd, LOCK_UN);
    close(pidfile_fd);
  }

  /* shutdown libraries*/
  curl_global_cleanup();

  return rc.isSuccess() ? 0 : 1;
}

void shutdown(int) {
  if (service) {
    service->kill();
  }
}

ReturnCode daemonize() {
#if defined(_WIN32)
#error "Application::daemonize() not yet implemented for windows"

#elif defined(__APPLE__) && defined(__MACH__)
  // FIXME: deprecated on OSX
  if (::daemon(true /*no chdir*/, true /*no close*/) < 0) {
    return ReturnCode::error("INIT_FAIL", "daemon() failed");
  }

#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
  if (::daemon(true /*no chdir*/, true /*no close*/) < 0) {
    return ReturnCode::error("INIT_FAIL", "daemon() failed");
  }

#else
#error Unsupported OS
#endif
  return ReturnCode::success();
}

<|endoftext|>


<commit_before>/** @file
 *
 * Client/server program for the basic controls test.
 *
 * Herein we implement a simple control system for an RGB LED connected to an
 * Arduino (running the sketch in rgb-led/rgb-led.ino, q.v.) attached to a
 * Raspberry Pi via I2C.  The client (master) sends control messages (consisting
 * of red, green, and blue channel data) to the server (slave) at a fixed rate.
 */
#include <cstdio>

/* ****************************************************************
 * Help text stuff.
 */
#if ! defined(__arm__)
#  define PRINT_USAGE(stream) fprintf(stream, "Usage: %s [OPTION]... ADDRESS PORT [EVDEV]\n", argv[0])

#  define HELP_TEXT "\
Simple controls-test program.\n\
\n\
Options:\n\
  -h	Show this help.\n\
\n\
Client mode is assumed when EVDEV argument is present; otherwise server mode is\n\
enabled.\n"
#else
#  define PRINT_USAGE(stream) fprintf(stream, "Usage: %s [OPTION]... ADDRESS PORT\n", argv[0])
#  define HELP_TEXT "\
Simple controls-test program: server-only build.\n\
\n\
Options:\n\
  -h	Show this help.\n\
\n"
#endif  /* ! defined(__arm__) */
/* **************************************************************** */

#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <crisp/comms/BasicNode.hh>
#include <crisp/comms/NodeServer.hh>

/* Define some aliases for our communication-types for convenience. */
using namespace crisp::comms;
typedef BasicNode<boost::asio::ip::tcp> Node;
typedef typename Node::Protocol Protocol;
typedef typename Protocol::endpoint Endpoint;

/** Pretty-print (using ANSI terminal codes) the red, green, and blue values in
 * a ModuleControl object to a file.
 *
 * @param fp File-pointer to print the values to.
 *
 * @param mc ModuleControl instance to fetch values from.
 */
static void
print_rgb(FILE* fp, const ModuleControl& mc)
{
  const crisp::comms::DataValue<>* dv ( nullptr );
  if ( (dv = mc.value_for("red")) != nullptr )
    fprintf(fp, " \033[1;31m%d\033[0m", dv->get<uint8_t>());
  if ( (dv = mc.value_for("green")) != nullptr )
    fprintf(fp, " \033[1;32m%d\033[0m", dv->get<uint8_t>());
  if ( (dv = mc.value_for("blue")) != nullptr )
    fprintf(fp, " \033[1;34m%d\033[0m", dv->get<uint8_t>());
}

#include "gnublin_i2c.hpp"

static int
run_server(boost::asio::io_service& service,
           const Endpoint& target_endpoint)

{
  crisp::comms::NodeServer<Node> server ( service, target_endpoint );

  gnublin_i2c i2c ( "/dev/i2c-1", 3 );

  /* I don't trust this "Gnublin" code (it's terribly written and I had to fix
     it to even get it to compile).   We'll be replacing it eventually. */
  i2c.setDevicefile("/dev/i2c-1");
  i2c.setAddress(3);

  if ( i2c.fail() )             /* Won't happen because the Gnublin stuff opens
                                   the device on-demand.  /Le sigh.../ */
    return 1;

  uint8_t values[3];
  memset(values, 0, sizeof(values));

  using namespace crisp::comms::keywords;

  /* Declare our interface configuration. */
  server.configuration.add_module( "LED", 3)
    .add_input<uint8_t>({ "red",   { _neutral = 0, _minimum = 0, _maximum = 255 } })
    .add_input<uint8_t>({ "green", { _neutral = 0, _minimum = 0, _maximum = 255 } })
    .add_input<uint8_t>({ "blue",  { _neutral = 0, _minimum = 0, _maximum = 255 } });

  /* Override the handler run when we receive a module-control packet. */
  server.dispatcher.module_control.received =
    [&](Node& node, const crisp::comms::ModuleControl& mc)
    {
      fprintf(stderr, "[0x%0x] \033[1;33mModule-control received\033[0m:", THREAD_ID);
      print_rgb(stderr, mc);
      fputc('\n', stderr);

      const crisp::comms::DataValue<>* dv ( nullptr );

      /* Update our input-value array. */
      if ( (dv = mc.value_for("red")) != nullptr )
          values[0] = dv->get<uint8_t>();
      if ( (dv = mc.value_for("green")) != nullptr )
          values[1] = dv->get<uint8_t>();
      if ( (dv = mc.value_for("blue")) != nullptr )
          values[2] = dv->get<uint8_t>();

      /* This call here is just so EW EW EW EW EW.  There's a reason for it, of
         course, but that reason is irrelevant with the way we're using I2C. */
      i2c.send(values[0], &values[1], 2);

      if ( i2c.fail() )
        {
          perror("write");
          abort();
        }
    };

  server.run();
  return 0;
}

/* Disable client functionality on the Raspberry Pi.

   _With_ this code (and using the old controller code based on the
   terribly-bloated Boost.Signals2 library), I had trouble getting the program
   to compile on the Pi without running out of memory.

   We don't need to support client mode on the Pi, so we might as well keep it
   disabled anyway.
 */
#if ! defined(__arm__)

#include <crisp/input/EvDevController.hh>
#include <float.h>

/** Given some angle, return a coterminal angle between 0 and two pi.
 */
static float
stdpos(float a)
{
  if ( a > 0 && a < 2 * M_PI )
    return a;
  else
    return std::fmod(a, 2 * M_PI) + (a > 0 ? 0 : 2 * M_PI);
}

/** Calculate the standard-position angle in the X-Y plane given by two
 *  cartesian coordinates.
 */
static float
theta(float x, float y)
{
  if ( std::abs(x) < 4 * FLT_EPSILON )
    {
      if ( std::abs(y) < 4 * FLT_EPSILON )
	return 0;
      else
	return y > 0 ? M_PI_2 : 3 * M_PI_2;
    }
  else if ( std::abs(y) < 4 * FLT_EPSILON )
    return x > 0 ? 0 : M_PI;
  else
    {
      float at ( std::atan(y/x) );
      if ( x < 0 )
        return stdpos(y < 0 ? at - M_PI : at + M_PI);
      else
        return stdpos(at);
    }
}

/** Update a ModuleControl instance to reflect the input coordinates.
 *
 * @param mc ModuleControl to update.
 *
 * @param x Value of the X input coordinate.
 *
 * @param y Value of the Y input coordinate.
 *
 */
void
update_control(crisp::comms::ModuleControl& mc,
               float x, float y)
{
  float
    angle = theta(x, y),
    intensity = std::min(std::hypot(x, y), 1.0f);
  fprintf(stderr, "angle %f, intensity %f:", angle * 180 / M_PI, intensity);

  mc.set<uint8_t>("red", 255 * intensity * (1 + std::cos(angle))/2);
  mc.set<uint8_t>("green", 255 * intensity * (1 + std::cos(angle + 2 * M_PI / 3))/2);
  mc.set<uint8_t>("blue", 255 * intensity * (1 + std::cos(angle + 4 * M_PI / 3))/2);

  print_rgb(stderr, mc);
  fputc('\n', stderr);
}

/** Run the client code, connecting to the specified endpoint and taking input
 *  from the specified Linux `evdev` input device.
 */
static int
run_client(boost::asio::io_service& service,
           const Endpoint& target_endpoint,
           const char* evdev)
{
  /* Create a socket, and try to connect it to the specified endpoint. */
  boost::system::error_code ec;
  typename Protocol::socket socket ( service );
  typename Protocol::resolver resolver ( service );

  boost::asio::connect(socket, resolver.resolve(target_endpoint), ec);

  if ( ec )
    { fprintf(stderr, "connect: %s\n", strerror(ec.value()));
      return 1; }
  else
    {
      /* We're going to be modifying the ModuleControl instance on-demand
         (whenever the input controller changes state), but sending it at a
         fixed rate.  Since this will be happening in different threads and
         there's no easy way to coordinate the memory access and modification
         otherwise,  we need a mutex to keep everything in sync.

         (If we didn't do this, we could e.g., get halfway through updating the
         module control instance, send a control packet based on that [likely
         invalid] data, and then finish updating.)
      */
      std::mutex mutex;
      ModuleControl mc;
      float x, y;

      /* Create the input device. */
      using namespace crisp::input;
      EvDevController controller ( evdev );
      assert(controller.axes.size() >= 2);

      /* Make sure the axes we'll be using are in absolute mode. */
      for ( size_t i ( 0 ); i < 2; ++i )
        if ( controller.axes[i].type != Axis::Type::ABSOLUTE )
          {                     /* Set up emulation for the axis. */
            controller.axes[i].mode = Axis::Type::ABSOLUTE;

            /* We need to manually initialize the absolute-axis raw-value
               configuration when an axis isn't actually absolute... */
            controller.axes[i].raw.minimum =
              - (controller.axes[i].raw.maximum = 256);
            controller.axes[i].raw.neutral = 0;

            /* set cubic mapping: value = 1 * x^3 + 0 * x^2 + 0 * x + 0.  */
            controller.axes[i].set_coefficients({1, 0, 0, 0});
          }

      /* Add event handlers for each of the axes we're interested in. */
      controller.axes[0].hook([&](const Axis& axis, Axis::State state)
                              {
                                std::unique_lock<std::mutex> lock ( mutex );
                                x = state.value;
                                update_control(mc, x, y);
                              });
      controller.axes[1].hook([&](const Axis& axis, Axis::State state)
                              {
                                std::unique_lock<std::mutex> lock ( mutex );
                                y = state.value;
                                update_control(mc, x, y);
                              });

      /* Instantiate the network node. */
      Node node ( std::move(socket), NodeRole::MASTER );

      /* Override the default configuration-response-recieved handler with
         this:

         - First, make sure that the received configuration is at least similar to what
           we expect; shut down the node if it isn't.

         - Store the received configuration.

         - Set up the module-control object to
      */
      node.dispatcher.configuration_response.received =
        [&](Node& _node, const Configuration& configuration)
        {
          node.configuration = configuration;
          mc.reset(&(node.configuration.modules[0]));

          using namespace crisp::util::literals;

          /* Set up the control packet to be sent at 25 Hz. */
          fprintf(stderr, "Setting up send action... ");
          node.scheduler.schedule(10_Hz,
                                  [&](crisp::util::PeriodicAction&) {
                                    std::unique_lock<std::mutex> lock ( mutex );
                                    node.send(mc);
                                    return;
                                  });
          fprintf(stderr, "done.");
        };

      /* clear the module-control-sent handler -- it's just a lot of spam. */
      node.dispatcher.module_control.sent = nullptr;

      std::atomic<bool> controller_run_flag ( true );

      /* On user interrupt (Ctrl+C), shut down the node and stop the
         controller's main loop.

         If the program doesn't stop, wiggle the controller a bit. (Current
         EvDevController implementation uses a blocking read, so it won't notice
         that the run flag has changed until it gets another event.)
      */
      boost::asio::signal_set ss ( service, SIGINT );
      ss.async_wait([&](const boost::system::error_code& error, int sig)
                    {
                      if ( ! error )
                        {
                          node.halt();
                          controller_run_flag = false;
                        }
                    });

      /* Queue a configuration-query message send. */
      node.send(MessageType::CONFIGURATION_QUERY);

      /* Start the controller and the network node. */
      std::thread controller_thread([&]() { controller.run(controller_run_flag); });
      node.run();
    }

  return 0;
}
#endif  /* ! defined(__arm__) */

int
main(int argc, char* argv[])
{
  /* Parse user options. */
  int c;
  while ( (c = getopt(argc, argv, "h")) != -1 )
    switch ( c )
      {
      case 'h':
	PRINT_USAGE(stdout);
	fprintf(stdout, HELP_TEXT);
	return 0;

      default:
	abort();
      }

  if ( argc - optind < 2 )
    {
      PRINT_USAGE(stderr);
      return 1;
    }

  /* Parse the address and port passed via the command line, and inform the user
     if they're doin' it wrong. */
  char* tail;
  boost::system::error_code pec;

  namespace ip = boost::asio::ip;

  ip::address address ( ip::address::from_string(argv[optind], pec) );

  int port ( strtoul(argv[optind + 1], &tail, 0) );

  if ( pec )
    { fprintf(stderr, "%s: %s (failed to parse IP address).\n", argv[optind], pec.message().c_str());
      return 1; }

  if ( *tail != '\0' )
    { fprintf(stderr, "%s: Need integer port number.\n", argv[optind + 1]);
      return 1; }

  /* Construct the target endpoint (IP address + port number) object. */
  using Protocol = Node::Protocol;
  typename Protocol::endpoint target_endpoint(address, port);

  /* Asio's io_service manages all the low-level details of parceling out work
     to IO worker threads in a thread-safe manner.  It's required for pretty
     much any network-related function, so we'll create one here.  */
  boost::asio::io_service service;

#if ! defined(__arm__)
  return ( argc - optind < 3 )
    ? run_server(service, target_endpoint)
    : run_client(service, target_endpoint, argv[optind+2]);
#else
  return run_server(service, target_endpoint);
#endif
}

<commit_msg>control-test/node.cc: updates for change to Signal-based message handlers<commit_after>/** @file
 *
 * Client/server program for the basic controls test.
 *
 * Herein we implement a simple control system for an RGB LED connected to an
 * Arduino (running the sketch in rgb-led/rgb-led.ino, q.v.) attached to a
 * Raspberry Pi via I2C.  The client (master) sends control messages (consisting
 * of red, green, and blue channel data) to the server (slave) at a fixed rate.
 */
#include <cstdio>

/* ****************************************************************
 * Help text stuff.
 */
#if ! defined(__arm__)
#  define PRINT_USAGE(stream) fprintf(stream, "Usage: %s [OPTION]... ADDRESS PORT [EVDEV]\n", argv[0])

#  define HELP_TEXT "\
Simple controls-test program.\n\
\n\
Options:\n\
  -h	Show this help.\n\
\n\
Client mode is assumed when EVDEV argument is present; otherwise server mode is\n\
enabled.\n"
#else
#  define PRINT_USAGE(stream) fprintf(stream, "Usage: %s [OPTION]... ADDRESS PORT\n", argv[0])
#  define HELP_TEXT "\
Simple controls-test program: server-only build.\n\
\n\
Options:\n\
  -h	Show this help.\n\
\n"
#endif  /* ! defined(__arm__) */
/* **************************************************************** */

#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <crisp/comms/BasicNode.hh>
#include <crisp/comms/NodeServer.hh>

/* Define some aliases for our communication-types for convenience. */
using namespace crisp::comms;
typedef BasicNode<boost::asio::ip::tcp> Node;
typedef typename Node::Protocol Protocol;
typedef typename Protocol::endpoint Endpoint;

/** Pretty-print (using ANSI terminal codes) the red, green, and blue values in
 * a ModuleControl object to a file.
 *
 * @param fp File-pointer to print the values to.
 *
 * @param mc ModuleControl instance to fetch values from.
 */
static void
print_rgb(FILE* fp, const ModuleControl& mc)
{
  const crisp::comms::DataValue<>* dv ( nullptr );
  if ( (dv = mc.value_for("red")) != nullptr )
    fprintf(fp, " \033[1;31m%d\033[0m", dv->get<uint8_t>());
  if ( (dv = mc.value_for("green")) != nullptr )
    fprintf(fp, " \033[1;32m%d\033[0m", dv->get<uint8_t>());
  if ( (dv = mc.value_for("blue")) != nullptr )
    fprintf(fp, " \033[1;34m%d\033[0m", dv->get<uint8_t>());
}

#include "gnublin_i2c.hpp"

static int
run_server(boost::asio::io_service& service,
           const Endpoint& target_endpoint)

{
  crisp::comms::NodeServer<Node> server ( service, target_endpoint );

  gnublin_i2c i2c ( "/dev/i2c-1", 3 );

  /* I don't trust this "Gnublin" code (it's terribly written and I had to fix
     it to even get it to compile).   We'll be replacing it eventually. */
  i2c.setDevicefile("/dev/i2c-1");
  i2c.setAddress(3);

  if ( i2c.fail() )             /* Won't happen because the Gnublin stuff opens
                                   the device on-demand.  /Le sigh.../ */
    return 1;

  uint8_t values[3];
  memset(values, 0, sizeof(values));

  using namespace crisp::comms::keywords;

  /* Declare our interface configuration. */
  server.configuration.add_module( "LED", 3)
    .add_input<uint8_t>({ "red",   { _neutral = 0, _minimum = 0, _maximum = 255 } })
    .add_input<uint8_t>({ "green", { _neutral = 0, _minimum = 0, _maximum = 255 } })
    .add_input<uint8_t>({ "blue",  { _neutral = 0, _minimum = 0, _maximum = 255 } });

  /* Disconnect the default "module-control received" handler, and connect our
     own. */
  server.dispatcher.module_control.received.clear();
  server.dispatcher.module_control.received.connect(
    [&](Node& node, const crisp::comms::ModuleControl& mc)
    {
      fprintf(stderr, "[0x%0x] \033[1;33mModule-control received\033[0m:", THREAD_ID);
      print_rgb(stderr, mc);
      fputc('\n', stderr);

      const crisp::comms::DataValue<>* dv ( nullptr );

      /* Update our input-value array. */
      if ( (dv = mc.value_for("red")) != nullptr )
          values[0] = dv->get<uint8_t>();
      if ( (dv = mc.value_for("green")) != nullptr )
          values[1] = dv->get<uint8_t>();
      if ( (dv = mc.value_for("blue")) != nullptr )
          values[2] = dv->get<uint8_t>();

      /* This call here is just so EW EW EW EW EW.  There's a reason for it, of
         course, but that reason is irrelevant with the way we're using I2C. */
      i2c.send(values[0], &values[1], 2);

      if ( i2c.fail() )
        {
          perror("write");
          abort();
        }
    });

  server.run();
  return 0;
}

/* Disable client functionality on the Raspberry Pi.

   _With_ this code (and using the old controller code based on the
   terribly-bloated Boost.Signals2 library), I had trouble getting the program
   to compile on the Pi without running out of memory.

   We don't need to support client mode on the Pi, so we might as well keep it
   disabled anyway.
 */
#if ! defined(__arm__)

#include <crisp/input/EvDevController.hh>
#include <float.h>

/** Given some angle, return a coterminal angle between 0 and two pi.
 */
static float
stdpos(float a)
{
  if ( a > 0 && a < 2 * M_PI )
    return a;
  else
    return std::fmod(a, 2 * M_PI) + (a > 0 ? 0 : 2 * M_PI);
}

/** Calculate the standard-position angle in the X-Y plane given by two
 *  cartesian coordinates.
 */
static float
theta(float x, float y)
{
  if ( std::abs(x) < 4 * FLT_EPSILON )
    {
      if ( std::abs(y) < 4 * FLT_EPSILON )
	return 0;
      else
	return y > 0 ? M_PI_2 : 3 * M_PI_2;
    }
  else if ( std::abs(y) < 4 * FLT_EPSILON )
    return x > 0 ? 0 : M_PI;
  else
    {
      float at ( std::atan(y/x) );
      if ( x < 0 )
        return stdpos(y < 0 ? at - M_PI : at + M_PI);
      else
        return stdpos(at);
    }
}

/** Update a ModuleControl instance to reflect the input coordinates.
 *
 * @param mc ModuleControl to update.
 *
 * @param x Value of the X input coordinate.
 *
 * @param y Value of the Y input coordinate.
 *
 */
void
update_control(crisp::comms::ModuleControl& mc,
               float x, float y)
{
  float
    angle = theta(x, y),
    intensity = std::min(std::hypot(x, y), 1.0f);
  fprintf(stderr, "angle %f, intensity %f:", angle * 180 / M_PI, intensity);

  mc.set<uint8_t>("red", 255 * intensity * (1 + std::cos(angle))/2);
  mc.set<uint8_t>("green", 255 * intensity * (1 + std::cos(angle + 2 * M_PI / 3))/2);
  mc.set<uint8_t>("blue", 255 * intensity * (1 + std::cos(angle + 4 * M_PI / 3))/2);

  print_rgb(stderr, mc);
  fputc('\n', stderr);
}

/** Run the client code, connecting to the specified endpoint and taking input
 *  from the specified Linux `evdev` input device.
 */
static int
run_client(boost::asio::io_service& service,
           const Endpoint& target_endpoint,
           const char* evdev)
{
  /* Create a socket, and try to connect it to the specified endpoint. */
  boost::system::error_code ec;
  typename Protocol::socket socket ( service );
  typename Protocol::resolver resolver ( service );

  boost::asio::connect(socket, resolver.resolve(target_endpoint), ec);

  if ( ec )
    { fprintf(stderr, "connect: %s\n", strerror(ec.value()));
      return 1; }
  else
    {
      /* We're going to be modifying the ModuleControl instance on-demand
         (whenever the input controller changes state), but sending it at a
         fixed rate.  Since this will be happening in different threads and
         there's no easy way to coordinate the memory access and modification
         otherwise,  we need a mutex to keep everything in sync.

         (If we didn't do this, we could e.g., get halfway through updating the
         module control instance, send a control packet based on that [likely
         invalid] data, and then finish updating.)
      */
      std::mutex mutex;
      ModuleControl mc;
      float x, y;

      /* Create the input device. */
      using namespace crisp::input;
      EvDevController controller ( evdev );
      assert(controller.axes.size() >= 2);

      /* Make sure the axes we'll be using are in absolute mode. */
      for ( size_t i ( 0 ); i < 2; ++i )
        if ( controller.axes[i].type != Axis::Type::ABSOLUTE )
          {                     /* Set up emulation for the axis. */
            controller.axes[i].mode = Axis::Type::ABSOLUTE;

            /* We need to manually initialize the absolute-axis raw-value
               configuration when an axis isn't actually absolute... */
            controller.axes[i].raw.minimum =
              - (controller.axes[i].raw.maximum = 256);
            controller.axes[i].raw.neutral = 0;

            /* set cubic mapping: value = 1 * x^3 + 0 * x^2 + 0 * x + 0.  */
            controller.axes[i].set_coefficients({1, 0, 0, 0});
          }

      /* Add event handlers for each of the axes we're interested in. */
      controller.axes[0].hook([&](const Axis& axis, Axis::State state)
                              {
                                std::unique_lock<std::mutex> lock ( mutex );
                                x = state.value;
                                update_control(mc, x, y);
                              });
      controller.axes[1].hook([&](const Axis& axis, Axis::State state)
                              {
                                std::unique_lock<std::mutex> lock ( mutex );
                                y = state.value;
                                update_control(mc, x, y);
                              });

      /* Instantiate the network node. */
      Node node ( std::move(socket), NodeRole::MASTER );

      /* Override the default configuration-response-recieved handler with
         this:

         - First, make sure that the received configuration is at least similar to what
           we expect; shut down the node if it isn't.

         - Store the received configuration.

         - Set up the module-control object to
      */
      node.dispatcher.configuration_response.received.connect(
        [&](Node& _node, const Configuration& configuration)
        {
          node.configuration = configuration;
          mc.reset(&(node.configuration.modules[0]));

          using namespace crisp::util::literals;

          /* Set up the control packet to be sent at 25 Hz. */
          fprintf(stderr, "Setting up send action... ");
          node.scheduler.schedule(10_Hz,
                                  [&](crisp::util::PeriodicAction&) {
                                    std::unique_lock<std::mutex> lock ( mutex );
                                    node.send(mc);
                                    return;
                                  });
          fprintf(stderr, "done.");
        });

      /* clear the module-control-sent handler -- it's just a lot of spam. */
      node.dispatcher.module_control.sent.clear();

      std::atomic<bool> controller_run_flag ( true );

      /* On user interrupt (Ctrl+C), shut down the node and stop the
         controller's main loop.

         If the program doesn't stop, wiggle the controller a bit. (Current
         EvDevController implementation uses a blocking read, so it won't notice
         that the run flag has changed until it gets another event.)
      */
      boost::asio::signal_set ss ( service, SIGINT );
      ss.async_wait([&](const boost::system::error_code& error, int sig)
                    {
                      if ( ! error )
                        {
                          node.halt();
                          controller_run_flag = false;
                        }
                    });

      /* Queue a configuration-query message send. */
      node.send(MessageType::CONFIGURATION_QUERY);

      /* Start the controller and the network node. */
      std::thread controller_thread([&]() { controller.run(controller_run_flag); });
      node.run();
    }

  return 0;
}
#endif  /* ! defined(__arm__) */

int
main(int argc, char* argv[])
{
  /* Parse user options. */
  int c;
  while ( (c = getopt(argc, argv, "h")) != -1 )
    switch ( c )
      {
      case 'h':
	PRINT_USAGE(stdout);
	fprintf(stdout, HELP_TEXT);
	return 0;

      default:
	abort();
      }

  if ( argc - optind < 2 )
    {
      PRINT_USAGE(stderr);
      return 1;
    }

  /* Parse the address and port passed via the command line, and inform the user
     if they're doin' it wrong. */
  char* tail;
  boost::system::error_code pec;

  namespace ip = boost::asio::ip;

  ip::address address ( ip::address::from_string(argv[optind], pec) );

  int port ( strtoul(argv[optind + 1], &tail, 0) );

  if ( pec )
    { fprintf(stderr, "%s: %s (failed to parse IP address).\n", argv[optind], pec.message().c_str());
      return 1; }

  if ( *tail != '\0' )
    { fprintf(stderr, "%s: Need integer port number.\n", argv[optind + 1]);
      return 1; }

  /* Construct the target endpoint (IP address + port number) object. */
  using Protocol = Node::Protocol;
  typename Protocol::endpoint target_endpoint(address, port);

  /* Asio's io_service manages all the low-level details of parceling out work
     to IO worker threads in a thread-safe manner.  It's required for pretty
     much any network-related function, so we'll create one here.  */
  boost::asio::io_service service;

#if ! defined(__arm__)
  return ( argc - optind < 3 )
    ? run_server(service, target_endpoint)
    : run_client(service, target_endpoint, argv[optind+2]);
#else
  return run_server(service, target_endpoint);
#endif
}

<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ruben Van Boxem
 *
 * 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 "test.h++"

#include "core/application.h++"

namespace
{
  using skui::test::assert;

  void test_arguments()
  {
    const auto& arguments = skui::core::application::instance().commandline_arguments;

    assert(arguments[0] == "skui is awesome", "arguments passed correctly.\n");
  }
}

int main(int argc, char* argv[])
{
  const skui::core::application app(argc, argv);

  test_arguments();

  return skui::test::exit_code;
}
<commit_msg>Add test for application::quit with return code<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ruben Van Boxem
 *
 * 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 "test.h++"

#include "core/application.h++"

#include <thread>

namespace
{
  using skui::test::assert;

  void test_arguments()
  {
    const auto& arguments = skui::core::application::instance().commandline_arguments;

    assert(arguments[0] == "skui is awesome", "arguments passed correctly.\n");
  }

  void test_execute_and_quit()
  {
    auto app = &skui::core::application::instance();
    constexpr int exit_code = 1;
    int return_value = 0;
    std::thread t([app, &return_value] { return_value = app->execute(); });

    app->quit(exit_code);

    t.join();

    assert(return_value = exit_code, "application::execute returns exit code passed to quit().");
  }
}

int main(int argc, char* argv[])
{
  const skui::core::application app(argc, argv);

  test_arguments();
  test_execute_and_quit();

  return skui::test::exit_code;
}
<|endoftext|>


<commit_before>#include "../../local/include/gtest/gtest.h"

#include <vector>

// uncomment to use threads
//#define THREADED

#ifdef THREADED

// Tron
//#define NUM_CORES 4
// Clue
#define NUM_CORES 12

#include <thread>
#include <future>

#endif  // THREADED

#include "genetic.h"
#include "genetic_circuit.h"
#include "circuit.h"
#include "utility.h"

TEST(InnocuousAttempt, XOR) {
  BooleanTable expected_o = {{false}, {true}, {false}, {true}};
  BooleanTable expected_o_bar = {{false}, {true}, {true}, {false}};

  std::minstd_rand rand(std::random_device{}());
  // std::minstd_rand0 rand(std::random_device{}());
  GeneticCircuit* c = new GeneticCircuit(2, 1, &rand);
  BooleanTable answer = c->evaluateAllInputs();
  int i = 0;

  while ((answer != expected_o) || (answer != expected_o_bar)) {
    ++i;
    delete c;
    c = new GeneticCircuit(2, 1, &rand);
    if (i % 100000 == 0) {
      std::cerr << "Iteration: " + std::to_string(i) << std::endl;
      std::cerr << *c << std::endl;
    }
    answer = c->evaluateAllInputs();
  }
  EXPECT_EQ(expected_o, answer);
  std::cerr << *c << std::endl;
  c->writeCircuitToFile();
}

TEST(InnocuousAttempt, FullAdder) {
  BooleanTable expected_o = {{false, false},
                             {false, true},
                             {false, true},
                             {true, false},
                             {false, true},
                             {true, false},
                             {true, false},
                             {true, true}};
  BooleanTable expected_o_bar = {{false, false},
                                 {true, false},
                                 {true, false},
                                 {false, true},
                                 {true, false},
                                 {false, true},
                                 {false, true},
                                 {true, true}};
  std::minstd_rand rand(std::random_device{}());
  // std::minstd_rand0 rand(std::random_device{}());
  GeneticCircuit* c = new GeneticCircuit(3, 2, &rand);
  BooleanTable answer = c->evaluateAllInputs();
  int i = 0;

  while ((answer != expected_o) || (answer != expected_o_bar)) {
    ++i;
    delete c;
    c = new GeneticCircuit(2, 2, &rand);
    if (i % 10000 == 0) {
      std::cerr << "Iteration: " + std::to_string(i) << std::endl;
      std::cerr << *c << std::endl;
    }
    answer = c->evaluateAllInputs();
  }
  EXPECT_EQ(expected_o, answer);
  std::cerr << *c << std::endl;
  c->writeCircuitToFile();
}

TEST(InnocuousAttempt, InvertInputs) {
  BooleanTable expected_o = {{true, true, true},
                             {true, true, false},
                             {true, false, true},
                             {true, false, false},
                             {false, true, true},
                             {false, true, false},
                             {false, false, true},
                             {false, false, false}};
  std::minstd_rand rand(std::random_device{}());
  // std::minstd_rand0 rand(std::random_device{}());
  GeneticCircuit* c = new GeneticCircuit(3, 3, &rand);
  BooleanTable answer = c->evaluateAllInputs();
  int i = 0;
  while ((answer != expected_o)) {
    ++i;
    delete c;
    c = new GeneticCircuit(3, 3, &rand);
    if (i % 10000 == 0) {
      std::cerr << "Iteration: " + std::to_string(i) << std::endl;
      std::cerr << *c << std::endl;
    }
    answer = c->evaluateAllInputs();
  }
  EXPECT_EQ(expected_o, answer);
  std::cerr << *c << std::endl;
  c->writeCircuitToFile();
}
<commit_msg>add VERBOSE_TEST option<commit_after>#include "../../local/include/gtest/gtest.h"

#include <vector>

// uncomment to use threads
//#define THREADED

#ifdef THREADED

// Tron
//#define NUM_CORES 4
// Clue
#define NUM_CORES 12

#include <thread>
#include <future>

#endif  // THREADED

#include "genetic.h"
#include "genetic_circuit.h"
#include "circuit.h"
#include "utility.h"

// #define VERBOSE_TEST

TEST(InnocuousAttempt, XOR) {
  BooleanTable expected_o = {{false}, {true}, {false}, {true}};
  BooleanTable expected_o_bar = {{false}, {true}, {true}, {false}};

  std::minstd_rand rand(std::random_device{}());
  // std::minstd_rand0 rand(std::random_device{}());
  GeneticCircuit* c = new GeneticCircuit(2, 1, &rand);
  BooleanTable answer = c->evaluateAllInputs();
  int i = 0;

  while ((answer != expected_o) || (answer != expected_o_bar)) {
    delete c;
    c = new GeneticCircuit(2, 1, &rand);
    answer = c->evaluateAllInputs();
    #ifdef VERBOSE_TEST
    ++i;
    if (i % 100000 == 0) {
      std::cerr << "Iteration: " + std::to_string(i) << std::endl;
      std::cerr << *c << std::endl;
    }
    #endif
  }
  EXPECT_EQ(expected_o, answer);
  std::cerr << *c << std::endl;
  c->writeCircuitToFile();
}

TEST(InnocuousAttempt, FullAdder) {
  BooleanTable expected_o = {{false, false},
                             {false, true},
                             {false, true},
                             {true, false},
                             {false, true},
                             {true, false},
                             {true, false},
                             {true, true}};
  BooleanTable expected_o_bar = {{false, false},
                                 {true, false},
                                 {true, false},
                                 {false, true},
                                 {true, false},
                                 {false, true},
                                 {false, true},
                                 {true, true}};
  std::minstd_rand rand(std::random_device{}());
  // std::minstd_rand0 rand(std::random_device{}());
  GeneticCircuit* c = new GeneticCircuit(3, 2, &rand);
  BooleanTable answer = c->evaluateAllInputs();
  int i = 0;

  while ((answer != expected_o) || (answer != expected_o_bar)) {
    delete c;
    c = new GeneticCircuit(2, 2, &rand);
    answer = c->evaluateAllInputs();
    #ifdef VERBOSE_TEST
    ++i;
    if (i % 10000 == 0) {
      std::cerr << "Iteration: " + std::to_string(i) << std::endl;
      std::cerr << *c << std::endl;
    }
    #endif
  }
  EXPECT_EQ(expected_o, answer);
  std::cerr << *c << std::endl;
  c->writeCircuitToFile();
}

TEST(InnocuousAttempt, InvertInputs) {
  BooleanTable expected_o = {{true, true, true},
                             {true, true, false},
                             {true, false, true},
                             {true, false, false},
                             {false, true, true},
                             {false, true, false},
                             {false, false, true},
                             {false, false, false}};
  std::minstd_rand rand(std::random_device{}());
  // std::minstd_rand0 rand(std::random_device{}());
  GeneticCircuit* c = new GeneticCircuit(3, 3, &rand);
  BooleanTable answer = c->evaluateAllInputs();
  int i = 0;
  while ((answer != expected_o)) {
    delete c;
    c = new GeneticCircuit(3, 3, &rand);
    answer = c->evaluateAllInputs();
    #ifdef VERBOSE_TEST
    ++i;
    if (i % 10000 == 0) {
      std::cerr << "Iteration: " + std::to_string(i) << std::endl;
      std::cerr << *c << std::endl;
    }
    #endif
  }
  EXPECT_EQ(expected_o, answer);
  std::cerr << *c << std::endl;
  c->writeCircuitToFile();
}
<|endoftext|>