qse/samples/app/MainApp.cpp

263 lines
7.0 KiB
C++

#include "MainApp.hpp"
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <qse/si/sio.h>
// ---------------------------------------------------------------------------
MainApp::Env::Env (MainApp* app, QSE::Mmgr* mmgr): SkvEnv(mmgr), app(app), log_type_preset(false), log_level_preset(false)
{
this->addItem (APP_ENV_LOG_TYPE,
APP_LOG_TYPE_FILE,
(ProbeProc)&MainApp::Env::probe_log_type);
this->addItem (
APP_ENV_LOG_LEVEL,
QSE_T("info+"),
(ProbeProc)&MainApp::Env::probe_log_level);
this->addItem (
APP_ENV_GATE_ADDRESSES,
APP_GATE_ADDRESSES,
(ProbeProc)&MainApp::Env::probe_gate_addresses);
this->addItem (
APP_ENV_GATE_MAX_CONNECTIONS,
QSE_Q(APP_GATE_MAX_CONNECTIONS),
(ProbeProc)&MainApp::Env::probe_gate_max_connections);
this->addItem (
APP_ENV_GATE_TIMEOUT,
APP_GATE_TIMEOUT,
(ProbeProc)&MainApp::Env::probe_gate_timeout);
}
int MainApp::Env::probe_log_type (const qse_char_t* v)
{
int tmask = 0;
const qse_char_t* p = v;
qse_cstr_t tok;
while (p)
{
p = qse_strtok(p, QSE_T(","), &tok);
if (qse_strxcmp(tok.ptr, tok.len, APP_LOG_TYPE_FILE) == 0)
tmask |= QSE_LOG_FILE;
else if (qse_strxcmp(tok.ptr, tok.len, APP_LOG_TYPE_SYSLOG) == 0)
tmask |= QSE_LOG_SYSLOG;
else if (qse_strxcmp(tok.ptr, tok.len, APP_LOG_TYPE_CONSOLE) == 0)
tmask |= QSE_LOG_CONSOLE;
else return -1;
};
if (!this->log_type_preset)
{
qse_log_target_data_t logtype;
this->app->getLogTarget (logtype);
this->app->setLogTarget (tmask, logtype);
}
return 0;
}
int MainApp::Env::probe_log_level (const qse_char_t* v)
{
if (v[0] == '\0' || qse_stristype(v, QSE_CTYPE_SPACE)) return -1;
int prio = qse_get_log_priority_by_name(v, QSE_T(","));
if (prio == 0) return -1; // unknown name inside
if (!this->log_level_preset) this->app->setLogPriorityMask(prio);
return 0;
}
int MainApp::Env::probe_gate_addresses (const qse_char_t* v)
{
if (v[0] == '\0' || qse_stristype(v, QSE_CTYPE_SPACE)) return -1;
this->gate_addresses.update (v);
return 0;
}
int MainApp::Env::probe_gate_max_connections (const qse_char_t* v)
{
if (v[0] == QSE_T('\0')) return -1;
if (!qse_stristype(v, QSE_CTYPE_DIGIT)) return -1;
/* don't care about the overflow */
unsigned int num = qse_strtoui(v, 10, QSE_NULL);
if (num < APP_GATE_MAX_CONNECTIONS_MIN || num > APP_GATE_MAX_CONNECTIONS_MAX) return -1;
this->gate_max_connections = num;
// TODO:
// this->app->setMaxTcpGateConnections (this->gate_max_connections);
return 0;
}
int MainApp::Env::probe_gate_timeout (const qse_char_t* v)
{
if (v[0] == QSE_T('\0')) return -1;
/* don't care about the overflow */
qse_ntime_t tmout;
if (qse_str_to_ntime(v, &tmout) <= -1) return -1;
this->gate_timeout = tmout;
return 0;
}
// ---------------------------------------------------------------------------
// TODO: get application name... use it for log files and for other purposes...
MainApp::MainApp (QSE::Mmgr* mmgr): QSE::App(mmgr), exit_code(0), guardian(true), conffile(APP_INI_FILE), env(this), tcp_gate(this)
{
}
MainApp::~MainApp ()
{
}
int MainApp::run (bool foreground)
{
QSE::App::SignalSet sigs;
sigs.set (SIGINT);
sigs.set (SIGHUP);
sigs.set (SIGTERM);
sigs.set (SIGUSR1);
sigs.set (SIGUSR2);
if (this->guardProcess(sigs, this->guardian) > 0)
{
this->acceptSignals (sigs);
this->setName (APP_NAME);
this->init_logger (foreground);
// assuming the above statements outside 'try..catch' throw no exceptions,
// no log message should be produced before configuration is loaded fully.
// any exceptions thrown before entering 'run()' is handled in Main.cc.
// for instance, setSysconfDir() which is supposed to be called in Main.cc
// may throw an exception. but it's treated as fatal error that prevents
// the program start-up in Main.cc.
try
{
this->chroot_if_needed ();
int x = this->load_config();
QSE_APP_LOG1 (this, QSE_LOG_INFO, QSE_T("starting application %d\n"), (int)getpid());
if (x <= -1) QSE_APP_LOG1 (this, QSE_LOG_WARNING, QSE_T("unable to load configuration from %js\n"), this->conffile.getData());
else QSE_APP_LOG1 (this, QSE_LOG_INFO, QSE_T("loaded configuration from %js\n"), this->conffile.getData());
this->tcp_gate.setBindAddress (this->gate_addresses.isEmpty()? this->env.getGateAddresses(): this->gate_addresses.getData());
if (this->tcp_gate.start() <= -1)
{
QSE_APP_LOG1 (this, QSE_LOG_ERROR, QSE_T("starting %d\n"), (int)getpid());
this->exit_code = 88;
goto done;
}
// TODO: start other worker threads and joins on them
this->tcp_gate.join ();
done:
QSE_APP_LOG2 (this, QSE_LOG_INFO, QSE_T("exiting application %d with code %d\n"), (int)getpid(), this->exit_code);
}
catch (QSE::Exception& e)
{
QSE_APP_LOG2 (this, QSE_LOG_INFO, QSE_T("terminating application for exception - %js - %js\n"), QSE_EXCEPTION_NAME(e), QSE_EXCEPTION_MSG(e));
}
catch (...)
{
QSE_APP_LOG0 (this, QSE_LOG_INFO, QSE_T("terminating application for unknown exception\n"));
}
this->discardSignals (sigs);
return this->exit_code;
}
this->neglectSignals (sigs, true);
return 0;
}
void MainApp::on_signal (int sig)
{
if (!this->isGuardian())
{
QSE_APP_LOG2 (this, QSE_LOG_INFO, QSE_T("terminating application %d on signal %d\n"), (int)getpid(), sig);
this->stop ((sig == SIGSEGV)? 99: 0);
}
}
void MainApp::stop (int code)
{
this->exit_code = code;
this->tcp_gate.stop ();
// TODO: stop all other worker threads...
}
void MainApp::setSysconfDir (const qse_char_t* v)
{
this->sysconfdir.update (v);
if (!this->sysconfdir.isEmpty())
{
this->conffile = this->sysconfdir;
if (this->conffile.getLastChar() != '/') this->conffile.append (QSE_T("/"));
this->conffile.append (QSE_T(APP_INI_FILE_));
}
}
void MainApp::init_logger (bool foreground)
{
qse_log_target_data_t logtgt;
memset (&logtgt, 0, QSE_SIZEOF(logtgt));
this->setLogOption (QSE_LOG_INCLUDE_PID | QSE_LOG_KEEP_FILE_OPEN);
this->setLogPriorityMask (QSE_LOG_ALL_PRIORITIES);
if (!this->loglevel.isEmpty() && this->env.probe_log_level(this->loglevel.getData()) >= 0) this->env.log_level_preset = true;
logtgt.file = this->logfile.isEmpty()? APP_LOG_FILE: this->logfile.getData();
this->setLogTarget (QSE_LOG_FILE, logtgt);
if (!this->logtype.isEmpty() && this->env.probe_log_type(this->logtype.getData()) >= 0) this->env.log_type_preset = true;
}
int MainApp::load_config ()
{
int n = this->env.load(this->conffile.getData());
// reset these to false so that ENVSET over the control channel
// take effect. these might be set to true in init_logger()
// and load_config() doesn't apply the relevant items when they are true
this->env.log_type_preset = false;
this->env.log_level_preset = false;
return n;
}
void MainApp::chroot_if_needed ()
{
const qse_char_t* root;
root = this->chroot_path.getData();
if (root[0] == '\0') root = this->env.getChroot();
if (root[0] != '\0')
{
if (this->chroot(root) <= -1)
{
QSE_APP_LOG1 (this, QSE_LOG_WARNING, QSE_T("unable to chroot to %js\n"), root);
}
else
{
QSE_APP_LOG1 (this, QSE_LOG_INFO, QSE_T("chrooted to %js\n"), root);
}
}
}