/* * This file is a part of Winix * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * 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 #include #include #include #include #include #include #include #include #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; }