/* tmd.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2020, 2021, 2026 Askar
    Almatov

    This file is part of Trafficmeter.

    Trafficmeter is free software: you can redistribute it and/or modify it under the terms of the GNU General
    Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    Trafficmeter 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 General Public
    License for more details.

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

#include <csignal>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <libiclass/time>
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>

#include "aggregate.h"
#include "dag.h"
#include "iface.h"
#include "monitor.h"
#include "now.h"
#include "param.h"
#include "path.h"
#include "resourcelimits.h"

using namespace iclass;
using namespace trafficmeter;

using std::boolalpha;
using std::cerr;
using std::cout;
using std::endl;
using std::exception;
using std::ifstream;
using std::noboolalpha;
using std::ofstream;
using std::runtime_error;
using std::string;
using std::vector;

constexpr const char*       CONFIG_SECTION_ = "TMD";

static const struct option  LONG_OPTIONS_[] =
{
    { "daemon",        no_argument,       nullptr, 'd' },
    { "conf",          required_argument, nullptr, 'c' },
    { "no-timestamps", no_argument,       nullptr, 'T' },
    { "version",       no_argument,       nullptr, 'v' },
    { "help",          no_argument,       nullptr, 'h' },
    { nullptr,         0,                 nullptr,  0  }
};

/**************************************************************************************************************/
int
main( int argc, char* argv[] )
{
    string  progName( Path(argv[0]).fileName() );
    Path    configFile( CONFIG_FILE );
    Path    pidFile( PID_FILE );
    Path    logFile( LOG_FILE );
    bool    isDaemonMode = false;
    int     opt;

    while ( (opt = getopt_long(argc,argv,"dc:Tvh",LONG_OPTIONS_,nullptr)) != -1 )
    {
        switch ( opt )
        {
            case 'd':

                isDaemonMode = true;
                break;

            case 'c':

                configFile = optarg;
                break;

            case 'T':

                Now::nothing();
                break;

            case 'v':

                cout <<
                    "Trafficmeter Daemon version " << VERSION << " for " << SYSTEM_TYPE << "\n"
                    "For info, please visit http://www.trafficmeter.org\n";

                return EXIT_SUCCESS;

            case 'h':
            default:

                cout <<
                    progName << " is the program to catch, save and index packets and their statistics\n"
                    "Usage: " << progName << " [options]\n"
                    "\t-d --daemon\t\tDaemon mode\n"
                    "\t-c --conf=<file>\tAlternate config file, default '" << CONFIG_FILE << "'\n"
                    "\t-T --no-timestamps\tDo not print timestamps in the log output\n"
                    "\t-v --version\t\tPrint version information\n"
                    "\t-h --help\t\tPrint help message\n"
                    "Signals:\n"
                    "\tUSR1\t\t\tReconfigure from tm.conf file\n"
                    "\tINT\t\t\tShutdown, Ctrl-C from console\n"
                    "\tTERM\t\t\tShutdown\n";

                return EXIT_SUCCESS;
        }
    }

    if ( pidFile.isExists() )
    {
        ifstream    pidInput( pidFile );
        int         pid;

        pidInput >> pid;

        if ( pidInput.good() && kill(pid, 0) == 0 )
        {
            cerr << progName << ": there's already the program running with pid=" << pid << "\n";
            return EXIT_FAILURE;
        }
    }

    Param           param( CONFIG_SECTION_ );
    ResourceLimits  limits;

    if ( !param.readFile(configFile) )
    {
        cerr << progName << ": cannot read " << configFile << "\n";
        return EXIT_FAILURE;
    }

    if ( param.sourceNames.empty() )
    {
        cerr << progName << ": no sources specified in " << configFile << " section " << CONFIG_SECTION_ << "\n";
        return EXIT_FAILURE;
    }

    if ( !limits.set() )
    {
        cerr << progName << ": cannot set resource limits\n";
        return EXIT_FAILURE;
    }

    if ( isDaemonMode )
    {
        int  logFd = open( logFile.c_str(), O_WRONLY|O_APPEND|O_CREAT, 0666 );

        if ( logFd < 0 )
        {
            isDaemonMode = false;
            cerr << progName << ": cannot write to " << logFile << ", set console mode" << endl;
        }
        else
        {
            int  pid = fork();

            if ( pid < 0 )
            {
                cerr << progName << ": cannot fork daemon\n";
                return EXIT_FAILURE;
            }
            else if ( pid > 0 )
            {
                return EXIT_SUCCESS;
            }

            close( STDIN_FILENO );
            close( STDOUT_FILENO );
            close( STDERR_FILENO );
            fcntl( logFd, F_DUPFD, STDOUT_FILENO  );
            fcntl( logFd, F_DUPFD, STDERR_FILENO  );
            setsid();
        }
    }

    ofstream( pidFile ) << getpid() << endl;
    sleep( 1 );  // to prevent coincidence of writing file names
    cout << "^\n\n" << Now() << "pid=" << getpid() << " tmd v" << VERSION << " started\n";

    for ( const string& result : limits.results() )
    {
        cout << "\tresource " << result << "\n";
    }

    cout <<
        "\tconfig SavePackets: " << boolalpha << param.shouldSavePackets << "\n"
        "\tconfig Erspan: " << param.shouldCheckErspan << noboolalpha << "\n"
        "\tconfig Verbosity: " << param.verbosity << "\n"
        "\tconfig Extractors: " << param.nExtractors << "\n"
        "\tconfig FlowWriters: " << param.nFlowWriters << "\n"
        "\tconfig StatWriters: " << param.nStatWriters << "\n"
        "\tconfig ExtractorChunks: " << param.nExtractorChunks << "\n"
        "\tconfig CollectionChunks: " << param.nCollectionChunks << "\n"
        "\tconfig FlowQuota: " << param.flowQuota << "\n"
        "\tconfig StatQuota: " << param.statQuota << "\n"
        "\tconfig Flow2Quota: " << param.flow2Quota << "\n"
        "\tconfig PacketQuota: " << param.packetQuota << endl;

    vector<Source*>     sources;

    for ( const string& sourceName : param.sourceNames )
    {
        Source*  source;

        try
        {
            if ( sourceName.find("dag") == 0 )
            {
                source = new Dag( sourceName );
            }
            else
            {
                source = new Iface( sourceName, true );
            }

            if ( param.shouldCheckErspan )
            {
                source->shouldCheckErspan();
            }

            cout << "\tsource name: " << sourceName << "\n\tsource info: " << source->info() << endl;

            if ( !source->isEthernet() )
            {
                throw runtime_error( "the source is not ethernet" );
            }

            sources.push_back( source );
        }
        catch ( exception& e )
        {
            cerr << "\tsource error: " << sourceName << " - " << e.what() << "\n";
            return EXIT_FAILURE;
        }
    }

    Monitor     monitor( param );
    Aggregate*  aggregate = new Aggregate( sources, param );
    sigset_t    signalSet;
    int         signal;

    monitor.bind( aggregate );
    sigfillset( &signalSet );
    sigprocmask( SIG_SETMASK, &signalSet, nullptr );
    monitor.start();
    usleep( 10000 );
    aggregate->start();

    while ( !sigwait(&signalSet, &signal) )
    {
        cout << "\tsignal received: " << signal << endl;

        if ( signal == SIGTERM || signal == SIGINT )
        {
            break;
        }

        if ( signal == SIGUSR1 )
        {
            if ( !param.readFile(configFile) )
            {
                cerr << "\tfile open error: " << configFile << ", no parameter changes" << endl;
                continue;
            }

            if ( !monitor.reconfigure(param) )
            {
                cout << "\tconfig has no changes" << endl;
            }
        }
    }

    aggregate->stop();
    monitor.stop();
    delete aggregate;

    for ( Source* source : sources )
    {
        delete source;
    }

    unlink( pidFile.c_str() );
    cout << Now() << "pid=" << getpid() << " tmd v" << VERSION << " finished\n\n$\n";
    return EXIT_SUCCESS;
}

