/*
* PtokaX - hub server for Direct Connect peer to peer network.
* Copyright (C) 2004-2022 Petr Kozelka, PPK at PtokaX dot org
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3
* as published by the Free Software Foundation.
* 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
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
#include "stdinc.h"
//---------------------------------------------------------------------------
#include "eventqueue.h"
#include "GlobalDataQueue.h"
#include "LanguageManager.h"
#include "LuaScriptManager.h"
#include "ServerManager.h"
#include "serviceLoop.h"
#include "SettingManager.h"
#include "utility.h"
#include <execinfo.h>
#include <signal.h>
#include <sstream>
#include <cxxabi.h>
#include <iostream>
#include <iomanip>
#include <ctime>
#include <fstream>
#include <civetweb.h>
using std::endl;
using std::ostringstream;
#include <prometheus/counter.h>
#include <prometheus/exposer.h>
#include <prometheus/registry.h>
//---------------------------------------------------------------------------
static bool bTerminatedBySignal = false;
static int iSignal = 0;
//---------------------------------------------------------------------------
static void SigHandler(int iSig)
{
bTerminatedBySignal = true;
iSignal = iSig;
// restore to default...
struct sigaction sigact;
sigact.sa_handler = SIG_DFL;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigaction(iSig, &sigact, NULL);
}
//---------------------------------------------------------------------------
static void showUsage()
{
printf("Usage: PtokaX [-d] [-v] [-m] [-c configdir] [-p pidfile]\n\n"
"Options:\n"
"\t-d\t\t- run as daemon.\n"
"\t-c configdir\t- absolute path to PtokaX configuration directory.\n"
"\t-p pidfile\t-p <pidfile> - path with filename where PtokaX PID will be stored.\n"
"\t-v\t\t- show PtokaX version with build date and time.\n"
"\t-m\t\t- show PtokaX configuration menu.\n"
"\t-use-syslog\t\t- Use syslog.\n"
"\t-use-log\t\t- Use log.\n"
);
}
//---------------------------------------------------------------------------
extern void* run_fly_server_test_port(void*);
//---------------------------------------------------------------------------
static void DoStackTrace()
{
void* addrlist[64];
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
if (!addrlen) {
std::cerr << "Stack backtrace is empty, possibly corrupt" << endl;
return;
}
char** symbollist = backtrace_symbols(addrlist, addrlen);
size_t funcnamesize = 256;
char* funcname = (char*)malloc(funcnamesize);
ostringstream bt;
for (int i = 1; i < addrlen; i++) {
char *begin_name = 0, *begin_offset = 0, *end_offset = 0;
for (char *p = symbollist[i]; *p; ++p) {
if (*p == '(') {
begin_name = p;
} else if (*p == '+') {
begin_offset = p;
} else if ((*p == ')') && begin_offset) {
end_offset = p;
break;
}
}
if (begin_name && begin_offset && end_offset && (begin_name < begin_offset)) {
*begin_name++ = '\0';
*begin_offset++ = '\0';
*end_offset = '\0';
int status;
char* ret = abi::__cxa_demangle(begin_name, funcname, &funcnamesize, &status);
if (!status) {
funcname = ret;
bt << symbollist[i] << ": " << funcname << " +" << begin_offset << endl;
} else {
bt << symbollist[i] << ": " << begin_name << "() +" << begin_offset << endl;
}
} else {
bt << symbollist[i] << endl;
}
}
free(funcname);
free(symbollist);
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::ostringstream oss;
oss << std::put_time(&tm, "%d-%m-%Y-%H-%M-%S");
const auto path = oss.str() + ".log";
std::cerr << "Stack backtrace:" << endl
<< endl
<< bt.str() << endl;
std::ofstream out_file(path, std::ios_base::out | std::ios::binary);
if (out_file.is_open())
{
out_file.write(bt.str().data(), bt.str().length());
}
}
#ifdef USE_HTTP_POST_CRASH_REPORT
cHTTPConn *http = new cHTTPConn(CRASH_SERV_ADDR, CRASH_SERV_PORT); // try to send via http
if (!http->mGood) {
std::cerr << "Failed connecting to crash server, please send above stack backtrace here: https://github.com/verlihub/verlihub/issues" << endl;
http->Close();
delete http;
http = NULL;
return;
}
ostringstream head;
head << "Hub-Info-Host: " << EraseNewLines(mC.hub_host) << "\r\n"; // remove new lines, they will break our request
head << "Hub-Info-Address: " << mAddr << ':' << mPort << "\r\n";
head << "Hub-Info-Uptime: " << (mTime.Sec() - mStartTime.Sec()) << "\r\n"; // uptime in seconds
head << "Hub-Info-Users: " << mUserCountTot << "\r\n";
ostringstream data;
data << "backtrace=" << StrToHex(bt.str());
if (http->Request("POST", "/vhcs.php", head.str(), data.str()) && http->mGood) {
std::cerr << "Successfully sent stack backtrace to crash server" << endl;
} else {
std::cerr << "Failed sending to crash server, please post above stack backtrace here: https://github.com/verlihub/verlihub/issues" << endl;
}
http->Close();
delete http;
http = NULL;
#endif // USE_HTTP_POST_CRASH_REPORT
}
static void mySigServHandler(int i)
{
std::cerr << "Received a " << i << " signal, doing stacktrace and quiting" << endl;
DoStackTrace();
exit(128 + i); // proper exit code for this signal
}
//---------------------------------------------------------------------------
void test_crash()
{
*(int*)1 = 0;
}
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
signal(SIGSEGV, mySigServHandler);
mg_start_thread(run_fly_server_test_port,nullptr);
bool bSetup = false;
bool bCrash = false;
char * sPidFile = NULL;
for (int i = 1; i < argc; i++)
{
if (strcasecmp(argv[i], "-crash") == 0)
{
printf("Crash before exit!\n");
bCrash = true; // *(int*)1 = 0;
}
else
if (strcasecmp(argv[i], "-d") == 0)
{
ServerManager::m_bDaemon = true;
}
else if (strcasecmp(argv[i], "-c") == 0)
{
if (++i == argc)
{
printf("Missing config directory!\n");
return EXIT_FAILURE;
}
if (argv[i][0] != '/')
{
printf("Config directory must be absolute path!\n");
return EXIT_FAILURE;
}
const size_t szLen = strlen(argv[i]);
if (argv[i][szLen - 1] == '/')
{
ServerManager::m_sPath = string(argv[i], szLen - 1);
}
else
{
ServerManager::m_sPath = string(argv[i], szLen);
}
if (DirExist(ServerManager::m_sPath.c_str()) == false)
{
if (mkdir(ServerManager::m_sPath.c_str(), 0755) == -1)
{
if (ServerManager::m_bDaemon == true)
{
syslog(LOG_USER | LOG_ERR, "Config directory not exist and can't be created!\n");
}
else
{
printf("Config directory not exist and can't be created!");
}
}
}
}
else if (strcasecmp(argv[i], "-v") == 0)
{
printf("%s built on %s %s\n", g_sPtokaXTitle, __DATE__, __TIME__);
return EXIT_SUCCESS;
}
else if (strcasecmp(argv[i], "-h") == 0)
{
showUsage();
return EXIT_SUCCESS;
}
else if (strcasecmp(argv[i], "-p") == 0)
{
if (++i == argc)
{
printf("Missing pid file!\n");
return EXIT_FAILURE;
}
sPidFile = argv[i];
}
else if (strcmp(argv[i], "-use-syslog") == 0)
{
extern bool g_isUseSyslog;
// g_isUseSyslog = true;
printf("\r\n[+] Use syslog for debug\r\n");
}
else if (strcmp(argv[i], "-use-log") == 0)
{
extern bool g_isUseLog;
g_isUseLog = true;
printf("\r\n[+] Use log for debug\r\n");
}
else if (strcasecmp(argv[i], "/generatexmllanguage") == 0)
{
LanguageManager::GenerateXmlExample();
return EXIT_SUCCESS;
}
else if (strcasecmp(argv[i], "-m") == 0)
{
bSetup = true;
}
else
{
printf("Unknown parameter %s.\n", argv[i]);
showUsage();
return EXIT_SUCCESS;
}
}
if (ServerManager::m_sPath.size() == 0)
{
char* home;
char curdir[PATH_MAX];
if (ServerManager::m_bDaemon == true && (home = getenv("HOME")) != NULL)
{
ServerManager::m_sPath = string(home) + "/.PtokaX";
if (DirExist(ServerManager::m_sPath.c_str()) == false)
{
if (mkdir(ServerManager::m_sPath.c_str(), 0755) == -1)
{
syslog(LOG_USER | LOG_ERR, "Config directory not exist and can't be created!\n");
}
}
}
else if (getcwd(curdir, PATH_MAX) != NULL)
{
ServerManager::m_sPath = curdir;
}
else
{
ServerManager::m_sPath = ".";
}
}
if (bSetup == true)
{
ServerManager::Initialize();
ServerManager::CommandLineSetup();
ServerManager::FinalClose();
return EXIT_SUCCESS;
}
if (ServerManager::m_bDaemon == true)
{
printf("Starting %s as daemon using %s as config directory.\n", g_sPtokaXTitle, ServerManager::m_sPath.c_str());
pid_t pid1 = fork();
if (pid1 == -1)
{
syslog(LOG_USER | LOG_ERR, "First fork failed!\n");
return EXIT_FAILURE;
}
else if (pid1 > 0)
{
return EXIT_SUCCESS;
}
if (setsid() == -1)
{
syslog(LOG_USER | LOG_ERR, "Setsid failed!\n");
return EXIT_FAILURE;
}
pid_t pid2 = fork();
if (pid2 == -1)
{
syslog(LOG_USER | LOG_ERR, "Second fork failed!\n");
return EXIT_FAILURE;
}
else if (pid2 > 0)
{
return EXIT_SUCCESS;
}
if (sPidFile != NULL)
{
FILE * fw = fopen(sPidFile, "w");
if (fw != NULL)
{
fprintf(fw, "%ld\n", (long)getpid());
fclose(fw);
}
}
if (chdir("/") == -1)
{
syslog(LOG_USER | LOG_ERR, "chdir failed!\n");
return EXIT_FAILURE;
}
else if (pid2 > 0)
{
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
if (open("/dev/null", O_RDWR) == -1)
{
syslog(LOG_USER | LOG_ERR, "Failed to open /dev/null!\n");
return EXIT_FAILURE;
}
if (dup(0) == -1)
{
syslog(LOG_USER | LOG_ERR, "First dup(0) failed!\n");
return EXIT_FAILURE;
}
if (dup(0) == -1)
{
syslog(LOG_USER | LOG_ERR, "Second dup(0) failed!\n");
return EXIT_FAILURE;
}
}
sigset_t sst;
sigemptyset(&sst);
sigaddset(&sst, SIGPIPE);
sigaddset(&sst, SIGURG);
sigaddset(&sst, SIGALRM);
if (ServerManager::m_bDaemon == true)
{
sigaddset(&sst, SIGHUP);
}
pthread_sigmask(SIG_BLOCK, &sst, NULL);
struct sigaction sigact;
sigact.sa_handler = SigHandler;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
if (sigaction(SIGINT, &sigact, NULL) == -1)
{
AppendDebugLog("%s - [ERR] Cannot create sigaction SIGINT in main\n");
exit(EXIT_FAILURE);
}
if (sigaction(SIGTERM, &sigact, NULL) == -1)
{
AppendDebugLog("%s - [ERR] Cannot create sigaction SIGTERM in main\n");
exit(EXIT_FAILURE);
}
if (sigaction(SIGQUIT, &sigact, NULL) == -1)
{
AppendDebugLog("%s - [ERR] Cannot create sigaction SIGQUIT in main\n");
exit(EXIT_FAILURE);
}
if (ServerManager::m_bDaemon == false && sigaction(SIGHUP, &sigact, NULL) == -1)
{
AppendDebugLog("%s - [ERR] Cannot create sigaction SIGHUP in main\n");
exit(EXIT_FAILURE);
}
// int *foo = (int*)-1; // make a bad pointer
// printf("%d\n", *foo); // causes segfault
ServerManager::Initialize();
if (ServerManager::Start() == false)
{
if (ServerManager::m_bDaemon == false)
{
printf("Server start failed!\n");
}
else
{
syslog(LOG_USER | LOG_ERR, "Server start failed!\n");
}
return EXIT_FAILURE;
}
else if (ServerManager::m_bDaemon == false)
{
printf("%s running...\n", g_sPtokaXTitle);
}
struct timespec sleeptime;
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = 100000000;
using namespace prometheus;
while (true)
{
ServiceLoop::m_Ptr->Looper();
if (ServerManager::m_bServerTerminated == true)
{
break;
}
if (bTerminatedBySignal == true)
{
if(bCrash)
{
test_crash();
}
extern int g_test_port_exit_flag;
g_test_port_exit_flag = 1;
if (ServerManager::m_bIsClose == true)
{
break;
}
std::string str("Received signal ");
if (iSignal == SIGINT)
{
str += "SIGINT";
}
else if (iSignal == SIGTERM)
{
str += "SIGTERM";
}
else if (iSignal == SIGQUIT)
{
str += "SIGQUIT";
}
else if (iSignal == SIGHUP)
{
str += "SIGHUP";
}
else
{
str += std::to_string(iSignal);
}
str += " ending...";
AppendLog(str.c_str());
ServerManager::m_bIsClose = true;
ServerManager::Stop();
// tell the scripts about the end
ScriptManager::m_Ptr->OnExit();
// send last possible global data
GlobalDataQueue::m_Ptr->SendFinalQueue();
ServerManager::FinalStop(true);
break;
}
nanosleep(&sleeptime, NULL);
static int g_count_rusage = 0;
if((++g_count_rusage % 10) == 0)
{
struct rusage ru;
if(!getrusage(RUSAGE_SELF, &ru))
{
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_maxrss",ru.ru_maxrss);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_ixrss",ru.ru_ixrss);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_idrss",ru.ru_idrss);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_isrss",ru.ru_isrss);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_nswap",ru.ru_nswap);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_inblock",ru.ru_inblock);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_oublock",ru.ru_oublock);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_majflt",ru.ru_majflt);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_minflt",ru.ru_minflt);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_nvcsw",ru.ru_nvcsw);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_nivcsw",ru.ru_nivcsw);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_nsignals",ru.ru_nsignals);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_utime",ru.ru_utime.tv_sec);
GlobalDataQueue::m_Ptr->PrometheusRusageValue("ru_stime",ru.ru_stime.tv_sec);
}
}
}
if (ServerManager::m_bDaemon == false)
{
printf("%s ending...\n", g_sPtokaXTitle);
}
else if (sPidFile != NULL)
{
unlink(sPidFile);
}
return EXIT_SUCCESS;
}
//---------------------------------------------------------------------------
↑ V1042 This file is marked with copyleft license, which requires you to open the derived source code.
↑ V547 Expression 'pid2 > 0' is always false.
↑ V773 The return value of the 'open' function is not saved. A resource leak is possible.