549 lines
14 KiB
C++
549 lines
14 KiB
C++
/*
|
|
* This file is a part of Winix
|
|
* and is distributed under the 2-Clause BSD licence.
|
|
* Author: Tomasz Sowa <t.sowa@ttmath.org>
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2008-2022, Tomasz Sowa
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sys/param.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <curl/curl.h>
|
|
#include "core/app.h"
|
|
#include "core/version.h"
|
|
#include "core/lock.h"
|
|
#include "utf8/utf8.h"
|
|
#include "mainoptions/mainoptionsparser.h"
|
|
#include "pikotools/version.h"
|
|
#include "tito.h"
|
|
#include "runstatus.h"
|
|
|
|
|
|
|
|
namespace Winix
|
|
{
|
|
|
|
/*
|
|
application object
|
|
*/
|
|
App app;
|
|
|
|
|
|
|
|
void print_version()
|
|
{
|
|
std::cout << "Winix " << WINIX_VER_MAJOR << '.' << WINIX_VER_MINOR << '.' << WINIX_VER_REVISION << std::endl;
|
|
std::cout << "Copyright (c) 2008-2022, Tomasz Sowa" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Components: " << std::endl;
|
|
std::cout << " pikotools: " << PIKOTOOLS_VERSION_MAJOR << '.' << PIKOTOOLS_VERSION_MINOR << '.' << PIKOTOOLS_VERSION_PATCH << std::endl;
|
|
std::cout << " morm: " << MORM_VERSION_MAJOR << '.' << MORM_VERSION_MINOR << '.' << MORM_VERSION_PATCH << std::endl;
|
|
std::cout << " ezc: " << EZC_VERSION_MAJOR << '.' << EZC_VERSION_MINOR << '.' << EZC_VERSION_PATCH << std::endl;
|
|
std::cout << " tito: " << TITO_VERSION_MAJOR << '.' << TITO_VERSION_MINOR << '.' << TITO_VERSION_PATCH << std::endl;
|
|
}
|
|
|
|
|
|
void print_syntax()
|
|
{
|
|
std::cout << "Winix " << WINIX_VER_MAJOR << '.' << WINIX_VER_MINOR << '.' << WINIX_VER_REVISION << std::endl;
|
|
std::cout << "Copyright (c) 2008-2022, Tomasz Sowa" << std::endl;
|
|
|
|
std::cout << "Usage: winix [options]" << std::endl;
|
|
std::cout << " -c : a path to the config file" << std::endl;
|
|
std::cout << " --config : a path to the config file" << std::endl;
|
|
std::cout << " --use-env : allow to use environment variables" << std::endl;
|
|
std::cout << " --dump-config : dump all read config options to stdout and exit" << std::endl;
|
|
std::cout << " -h : print usage information" << std::endl;
|
|
std::cout << " --help : print usage information" << std::endl;
|
|
std::cout << " -? : print usage information" << std::endl;
|
|
std::cout << " -v : print version" << std::endl;
|
|
std::cout << " --version : print version" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "At least one -c or --config parameter is required, use -c or --config option" << std::endl;
|
|
std::cout << "multiple times to load more than one config." << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Environment variables must be prefixed with winix_ to be loaded by winix." << std::endl;
|
|
std::cout << "The winix_ prefix is then removed and the key value converted to lowercase." << std::endl;
|
|
std::cout << "Sample:" << std::endl;
|
|
std::cout << "evn WINIX_MY_OPTION=TEST123test winix -c config_file" << std::endl;
|
|
std::cout << "This will add my_option to the config with value TEST123test." << std::endl;
|
|
std::cout << "Environment variables are read last so they will overwrite the values" << std::endl;
|
|
std::cout << "from the configuration files." << std::endl;
|
|
}
|
|
|
|
|
|
|
|
RunStatus ParseParameters(int argc, const char ** argv, pt::Space & options)
|
|
{
|
|
pt::Space arguments;
|
|
pt::MainOptionsParser options_parser;
|
|
RunStatus run_status;
|
|
|
|
arguments.add(L"c", 1);
|
|
arguments.add(L"config", 1);
|
|
|
|
pt::MainOptionsParser::Status status = options_parser.parse(argc, argv, options, arguments);
|
|
|
|
if( status != pt::MainOptionsParser::status_ok )
|
|
{
|
|
Winix::print_syntax();
|
|
run_status.exit_code = RunStatus::EXIT_CODE_PARAMETERS_SYNTAX_ERROR;
|
|
run_status.should_continue = false;
|
|
}
|
|
else
|
|
{
|
|
if( options.has_key(L"h") || options.has_key(L"help") || options.has_key(L"?") )
|
|
{
|
|
Winix::print_syntax();
|
|
run_status.should_continue = false;
|
|
}
|
|
|
|
if( options.has_key(L"v") || options.has_key(L"version") )
|
|
{
|
|
Winix::print_version();
|
|
run_status.should_continue = false;
|
|
}
|
|
}
|
|
|
|
return run_status;
|
|
}
|
|
|
|
|
|
void CreateNewDescriptor(int des_dst, int flags)
|
|
{
|
|
int descriptor;
|
|
|
|
descriptor = open("/dev/null", flags | O_NOCTTY);
|
|
|
|
if( descriptor != -1 )
|
|
{
|
|
dup2(descriptor, des_dst);
|
|
|
|
if( descriptor != des_dst )
|
|
close(descriptor);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CloseDescriptors()
|
|
{
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
app.stdout_is_closed = true;
|
|
|
|
CreateNewDescriptor(0, O_RDONLY);
|
|
CreateNewDescriptor(1, O_WRONLY);
|
|
CreateNewDescriptor(2, O_WRONLY);
|
|
}
|
|
|
|
|
|
void LogInfo(Log & log, LogManipulators log_level, const char * msg, bool put_version, const char * msg2)
|
|
{
|
|
log << log_level;
|
|
log.PrintDate(pt::Date(std::time(0)));
|
|
log << ' ' << msg;
|
|
|
|
if( put_version )
|
|
{
|
|
log << ' ' << WINIX_VER_MAJOR
|
|
<< '.' << WINIX_VER_MINOR
|
|
<< '.' << WINIX_VER_REVISION;
|
|
}
|
|
|
|
log << ' ' << msg2 << logend;
|
|
}
|
|
|
|
|
|
void SavePidFile(Log & log)
|
|
{
|
|
if( !app.config.pid_file.empty() )
|
|
{
|
|
std::string file_name;
|
|
pt::wide_to_utf8(app.config.pid_file, file_name);
|
|
std::ofstream file(file_name);
|
|
|
|
if( !file )
|
|
{
|
|
log << log1 << "I cannot save the pid to a file: " << app.config.pid_file << logend;
|
|
}
|
|
else
|
|
{
|
|
file << getpid() << "\n";
|
|
file.close();
|
|
log << log3 << "Process pid saved to: " << app.config.pid_file << logend;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RemovePidFile()
|
|
{
|
|
if( !app.config.pid_file.empty() )
|
|
{
|
|
std::string file_name;
|
|
pt::wide_to_utf8(app.config.pid_file, file_name);
|
|
unlink(file_name.c_str());
|
|
}
|
|
}
|
|
|
|
|
|
bool ReadConfigs(const pt::Space::TableType & table, size_t & config_read)
|
|
{
|
|
bool status = true;
|
|
|
|
for(const pt::Space * config : table)
|
|
{
|
|
if( config->is_table() && config->table_size() == 1 )
|
|
{
|
|
std::wstring config_file = config->value.value_table[0]->to_wstr();
|
|
config_read += 1;
|
|
|
|
if( !app.config.ReadConfig(config_file) )
|
|
{
|
|
status = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
void ReadEnvStringOption(const char * option)
|
|
{
|
|
const char winix_prefix[] = "winix_";
|
|
size_t prefix_len = sizeof(winix_prefix) - 1; // null terminating table
|
|
std::wstring opt, val;
|
|
|
|
if( pt::is_substr_nc(winix_prefix, option) )
|
|
{
|
|
pt::utf8_to_wide(option + prefix_len, opt);
|
|
std::wstring::size_type sep = opt.find('=');
|
|
|
|
if( sep != std::wstring::npos )
|
|
{
|
|
val = opt.substr(sep + 1);
|
|
opt.erase(sep);
|
|
}
|
|
|
|
TemplatesFunctions::locale.ToSmall(opt);
|
|
app.config.space.add(opt, val);
|
|
}
|
|
}
|
|
|
|
|
|
bool ReadEnvJsonOption(const char * option)
|
|
{
|
|
const char winix_prefix[] = "winixjson_";
|
|
size_t prefix_len = sizeof(winix_prefix) - 1; // null terminating table
|
|
std::wstring opt;
|
|
bool status = true;
|
|
|
|
if( pt::is_substr_nc(winix_prefix, option) )
|
|
{
|
|
pt::utf8_to_wide(option + prefix_len, opt);
|
|
std::wstring::size_type sep = opt.find('=');
|
|
|
|
if( sep != std::wstring::npos )
|
|
{
|
|
pt::SpaceParser parser;
|
|
pt::Space value_space;
|
|
pt::SpaceParser::Status parse_status = parser.parse_json(opt.c_str() + sep + 1, value_space, false);
|
|
opt.erase(sep);
|
|
TemplatesFunctions::locale.ToSmall(opt);
|
|
|
|
switch(parse_status)
|
|
{
|
|
case pt::SpaceParser::Status::ok:
|
|
app.config.space.add(opt, value_space);
|
|
break;
|
|
|
|
default:
|
|
std::cout << "I cannot parse the " << option << " environment variable as JSON" << std::endl;
|
|
status = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
bool ReadConfigFromEnv(const char ** env)
|
|
{
|
|
bool status = true;
|
|
|
|
for(size_t i = 0 ; env[i] ; ++i)
|
|
{
|
|
ReadEnvStringOption(env[i]);
|
|
|
|
if( !ReadEnvJsonOption(env[i]) )
|
|
status = false;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ReadConfigs(const pt::Space & options, const char ** env)
|
|
{
|
|
size_t config_read = 0;
|
|
bool status = true;
|
|
const pt::Space::TableType * table = options.get_table(L"c");
|
|
app.config.space.clear();
|
|
|
|
if( table )
|
|
{
|
|
status = status && ReadConfigs(*table, config_read);
|
|
}
|
|
|
|
table = options.get_table(L"config");
|
|
|
|
if( table )
|
|
{
|
|
status = status && ReadConfigs(*table, config_read);
|
|
}
|
|
|
|
if( config_read == 0 )
|
|
{
|
|
std::cout << "You have to provide a config file with c parameter" << std::endl;
|
|
Winix::print_syntax();
|
|
return false;
|
|
}
|
|
|
|
if( options.has_key(L"use-env") )
|
|
{
|
|
status = status && ReadConfigFromEnv(env);
|
|
}
|
|
|
|
app.config.AssignValuesFromSpace();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
bool InitCurlLibrary()
|
|
{
|
|
/*
|
|
*
|
|
* from documentation https://curl.se/libcurl/c/curl_global_init.html
|
|
* This function is thread-safe since libcurl 7.84.0 if curl_version_info has the CURL_VERSION_THREADSAFE
|
|
* feature bit set (most platforms).
|
|
*
|
|
* If this is not thread-safe, you must not call this function when any other thread in the program
|
|
* (i.e. a thread sharing the same memory) is running. This does not just mean no other thread that
|
|
* is using libcurl. Because curl_global_init calls functions of other libraries that are similarly
|
|
* thread unsafe, it could conflict with any other thread that uses these other libraries.
|
|
*
|
|
*/
|
|
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
|
|
bool ok = (code == CURLE_OK);
|
|
|
|
if( !ok )
|
|
{
|
|
std::cout << "Cannot initialize curl library, exiting" << std::endl;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
void CleanupCurlLibrary()
|
|
{
|
|
curl_global_cleanup();
|
|
}
|
|
|
|
|
|
|
|
RunStatus InitializeWinix(Log & log, pt::Space & options, const char ** env)
|
|
{
|
|
RunStatus run_status;
|
|
|
|
if( !InitCurlLibrary() )
|
|
{
|
|
run_status.exit_code = RunStatus::EXIT_CODE_CANNOT_INITIALIZE_CURL;
|
|
run_status.should_continue = false;
|
|
return run_status;
|
|
}
|
|
|
|
if( !ReadConfigs(options, env) )
|
|
{
|
|
pt::WTextStream * log_buffer = log.get_log_buffer();
|
|
|
|
// we need to print the buffer by hand because the logger
|
|
// is not fully initialized yet
|
|
if( log_buffer && !log_buffer->empty() )
|
|
{
|
|
pt::wide_stream_to_utf8(*log_buffer, std::cout);
|
|
}
|
|
|
|
run_status.exit_code = RunStatus::EXIT_CODE_CANNOT_CORRECTLY_READ_CONFIG;
|
|
run_status.should_continue = false;
|
|
return run_status;
|
|
}
|
|
|
|
if( options.has_key(L"dump-config") )
|
|
{
|
|
pt::WTextStream * log_buffer = log.get_log_buffer();
|
|
|
|
if( log_buffer )
|
|
{
|
|
log << "all read config options (some of the values could have been modified by winix"
|
|
<< " but the modified values are not printed here):" << logend;
|
|
app.config.space.serialize_to_space_stream(*log_buffer, true);
|
|
log << logend;
|
|
pt::wide_stream_to_utf8(*log_buffer, std::cout);
|
|
}
|
|
|
|
run_status.should_continue = false;
|
|
return run_status;
|
|
}
|
|
|
|
app.InitLoggers();
|
|
|
|
if( app.stdout_is_closed || app.config.demonize )
|
|
app.config.log_stdout = false;
|
|
|
|
if( !app.config.log_stdout )
|
|
CloseDescriptors();
|
|
|
|
if( app.config.demonize && !app.Demonize() )
|
|
{
|
|
log << logsave;
|
|
run_status.exit_code = RunStatus::EXIT_CODE_CANNOT_DEMONIZE;
|
|
run_status.should_continue = false;
|
|
return run_status;
|
|
}
|
|
|
|
if( !app.InitFCGI() )
|
|
{
|
|
/*
|
|
* WARNING:
|
|
* when there is a problem with initializing FastCGI FCGX_OpenSocket() will call exit()
|
|
* and we never reach here
|
|
*/
|
|
log << logsave;
|
|
run_status.exit_code = RunStatus::EXIT_CODE_CANNOT_INITIALIZE_FASTCGI;
|
|
run_status.should_continue = false;
|
|
return run_status;
|
|
}
|
|
|
|
if( !app.DropPrivileges() )
|
|
{
|
|
log << logsave;
|
|
run_status.exit_code = RunStatus::EXIT_CODE_CANNOT_DROP_PRIVILEGES;
|
|
run_status.should_continue = false;
|
|
return run_status;
|
|
}
|
|
|
|
app.LogUserGroups();
|
|
SavePidFile(log);
|
|
|
|
// app.Init() starts other threads as well (they will be waiting on the lock)
|
|
if( !app.Init() )
|
|
{
|
|
run_status.exit_code = RunStatus::EXIT_CODE_CANNOT_INITIALIZE_APPLICATION;
|
|
run_status.should_continue = false;
|
|
return run_status;
|
|
}
|
|
|
|
return run_status;
|
|
}
|
|
|
|
|
|
void UninitializeWinix(Log & log)
|
|
{
|
|
Winix::RemovePidFile();
|
|
log << Winix::logsave;
|
|
Winix::CleanupCurlLibrary();
|
|
}
|
|
|
|
|
|
|
|
} // namespace Winix
|
|
|
|
|
|
|
|
int main(int argc, const char ** argv, const char ** env)
|
|
{
|
|
using Winix::app;
|
|
|
|
Winix::RunStatus run_status;
|
|
pt::Space options;
|
|
|
|
std::srand(std::time(0));
|
|
app.system.system_start = time(0);
|
|
|
|
run_status = Winix::ParseParameters(argc, argv, options);
|
|
|
|
if( !run_status.should_continue )
|
|
return run_status.exit_code;
|
|
|
|
Winix::Log & log = app.GetMainLog();
|
|
Winix::LogInfo(log, Winix::log3, "UTC booting Winix", true, ""); // date will be printed as UTC because the time zones are not loaded yet
|
|
|
|
run_status = Winix::InitializeWinix(log, options, env);
|
|
|
|
if( run_status.should_continue )
|
|
{
|
|
app.StartThreads();
|
|
// now we are in multi threaded environment, we should use our locking mechanism
|
|
|
|
// saving all starting logs (logger can be used without locking)
|
|
Winix::LogInfo(log, Winix::log1, "Winix", true, "started");
|
|
log << Winix::logsave;
|
|
|
|
// start the main loop
|
|
app.Start();
|
|
|
|
// close winix
|
|
app.Close();
|
|
// now all other threads are terminated, we are in single threaded environment again
|
|
}
|
|
|
|
Winix::LogInfo(log, Winix::log1, "Winix", true, "stopped");
|
|
UninitializeWinix(log);
|
|
|
|
return run_status.exit_code;
|
|
}
|
|
|