From 132e59db51cb94cbd6dc2034d2a4b87b432b1866 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 3 Jun 2023 03:16:32 +0200 Subject: [PATCH] Support for reloading config from disk, writing config to disk after 60 seconds of no changes, reloading config from disk on API call request --- lib/comms.h | 15 -- lib/defines.h | 17 ++ lib/timing.cpp | 11 + lib/timing.h | 1 + src/controller/controller.cpp | 304 ++++++++++---------------- src/controller/controller_api.cpp | 8 +- src/controller/controller_storage.cpp | 193 ++++++++++++++-- src/controller/controller_storage.h | 12 +- 8 files changed, 339 insertions(+), 222 deletions(-) diff --git a/lib/comms.h b/lib/comms.h index 9e907124..8dfed832 100644 --- a/lib/comms.h +++ b/lib/comms.h @@ -3,21 +3,6 @@ #include "shared_memory.h" #include "util.h" -#define COMM_STATUS_SOURCE 0x80 -#define COMM_STATUS_DONOTTRACK 0x40 -#define COMM_STATUS_DISCONNECT 0x20 -#define COMM_STATUS_REQDISCONNECT 0x10 -#define COMM_STATUS_ACTIVE 0x1 -#define COMM_STATUS_INVALID 0x0 -#define SESS_BUNDLE_DEFAULT_VIEWER 14 -#define SESS_BUNDLE_DEFAULT_OTHER 15 -#define SESS_DEFAULT_STREAM_INFO_MODE 1 -#define SESS_HTTP_AS_VIEWER 1 -#define SESS_HTTP_AS_OUTPUT 2 -#define SESS_HTTP_DISABLED 3 -#define SESS_HTTP_AS_UNSPECIFIED 4 -#define SESS_TKN_DEFAULT_MODE 15 - #define COMM_LOOP(comm, onActive, onDisconnect) \ {\ diff --git a/lib/defines.h b/lib/defines.h index 71f15bd7..9337a0b6 100644 --- a/lib/defines.h +++ b/lib/defines.h @@ -285,3 +285,20 @@ static inline void show_stackframe(){} #define NEW_TRACK_ID 0x80000000 #define QUICK_NEGOTIATE 0xC0000000 + +// Session and Comm library related constants +#define COMM_STATUS_SOURCE 0x80 +#define COMM_STATUS_DONOTTRACK 0x40 +#define COMM_STATUS_DISCONNECT 0x20 +#define COMM_STATUS_REQDISCONNECT 0x10 +#define COMM_STATUS_ACTIVE 0x1 +#define COMM_STATUS_INVALID 0x0 +#define SESS_BUNDLE_DEFAULT_VIEWER 14 +#define SESS_BUNDLE_DEFAULT_OTHER 15 +#define SESS_DEFAULT_STREAM_INFO_MODE 1 +#define SESS_HTTP_AS_VIEWER 1 +#define SESS_HTTP_AS_OUTPUT 2 +#define SESS_HTTP_DISABLED 3 +#define SESS_HTTP_AS_UNSPECIFIED 4 +#define SESS_TKN_DEFAULT_MODE 15 + diff --git a/lib/timing.cpp b/lib/timing.cpp index 4cd99f12..3487d393 100644 --- a/lib/timing.cpp +++ b/lib/timing.cpp @@ -5,6 +5,7 @@ #include #include #include //for gettimeofday +#include #include //for time and nanosleep #include #include @@ -166,3 +167,13 @@ std::string Util::getDateString(uint64_t epoch){ strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S %z", timeinfo); return std::string(buffer); } + +/// Gets unix time of last file modification, or 0 if this information is not available for any reason +uint64_t Util::getFileUnixTime(const std::string & filename){ + struct stat fInfo; + if (stat(filename.c_str(), &fInfo) == 0){ + return fInfo.st_mtime; + } + return 0; +} + diff --git a/lib/timing.h b/lib/timing.h index 886950ae..a0eff784 100644 --- a/lib/timing.h +++ b/lib/timing.h @@ -21,4 +21,5 @@ namespace Util{ uint64_t getMSFromUTCString(std::string UTCString); uint64_t getUTCTimeDiff(std::string UTCString, uint64_t epochMillis); std::string getDateString(uint64_t epoch = 0); + uint64_t getFileUnixTime(const std::string & filename); }// namespace Util diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp index 25502cbb..d6c4a928 100644 --- a/src/controller/controller.cpp +++ b/src/controller/controller.cpp @@ -41,6 +41,8 @@ #define COMPILED_PASSWORD "" #endif +uint64_t lastConfRead = 0; + /// the following function is a simple check if the user wants to proceed to fix (y), ignore (n) or /// abort on (a) a question static inline char yna(std::string &user_input){ @@ -72,12 +74,49 @@ void createAccount(std::string account){ } /// Status monitoring thread. -/// Will check outputs, inputs and converters every three seconds +/// Checks status of "protocols" (listening outputs) +/// Updates config from disk when changed +/// Writes config to disk after some time of no changes void statusMonitor(void *np){ Controller::loadActiveConnectors(); while (Controller::conf.is_active){ - // this scope prevents the configMutex from being locked constantly + + // Check configuration file last changed time + uint64_t confTime = Util::getFileUnixTime(Controller::conf.getString("configFile")); + if (Controller::Storage.isMember("config_split")){ + jsonForEach(Controller::Storage["config_split"], cs){ + if (cs->isString()){ + uint64_t subTime = Util::getFileUnixTime(cs->asStringRef()); + if (subTime && subTime > confTime){confTime = subTime;} + } + } + } + // If we recently wrote, assume we know the contents since that time, too. + if (lastConfRead < Controller::lastConfigWrite){lastConfRead = Controller::lastConfigWrite;} + // If the config has changed, update Controller::lastConfigChange { + JSON::Value currConfig; + Controller::getConfigAsWritten(currConfig); + if (Controller::lastConfigSeen != currConfig){ + Controller::lastConfigChange = Util::epoch(); + Controller::lastConfigSeen = currConfig; + } + } + // Read from disk if they are newer than our last read + if (confTime && confTime > lastConfRead){ + INFO_MSG("Configuration files changed - reloading configuration from disk"); + tthread::lock_guard guard(Controller::configMutex); + Controller::readConfigFromDisk(); + lastConfRead = Controller::lastConfigChange; + } + // Write to disk if we have made no changes in the last 60 seconds and the files are older than the last change + if (Controller::lastConfigChange > Controller::lastConfigWrite && Controller::lastConfigChange < Util::epoch() - 60){ + tthread::lock_guard guard(Controller::configMutex); + Controller::writeConfigToDisk(); + if (lastConfRead < Controller::lastConfigWrite){lastConfRead = Controller::lastConfigWrite;} + } + + { // this scope prevents the configMutex from being locked constantly tthread::lock_guard guard(Controller::configMutex); // checks online protocols, reports changes to status if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"], Controller::capabilities)){ @@ -86,7 +125,8 @@ void statusMonitor(void *np){ // checks stream statuses, reports changes to status Controller::CheckAllStreams(Controller::Storage["streams"]); } - Util::sleep(3000); // wait at least 3 seconds + + Util::sleep(3000); // wait at most 3 seconds } if (Util::Config::is_restarting){ Controller::prepareActiveConnectorsForReload(); @@ -137,6 +177,75 @@ void handleUSR1Parent(int signum, siginfo_t *sigInfo, void *ignore){ Util::Config::is_restarting = true; } +bool interactiveFirstTimeSetup(){ + // check for username + if (!Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){ + std::string in_string = ""; + while (yna(in_string) == 'x' && Controller::conf.is_active){ + std::cout << "Account not set, do you want to create an account? (y)es, (n)o, (a)bort: "; + std::cout.flush(); + std::getline(std::cin, in_string); + switch (yna(in_string)){ + case 'y':{ + // create account + std::string usr_string = ""; + while (!(Controller::Storage.isMember("account") && Controller::Storage["account"].size() > 0) && + Controller::conf.is_active){ + std::cout << "Please type in the username, a colon and a password in the following " + "format; username:password" + << std::endl + << ": "; + std::cout.flush(); + std::getline(std::cin, usr_string); + createAccount(usr_string); + } + }break; + case 'a': return false; // abort bootup + case 't':{ + createAccount("test:test"); + if ((Controller::capabilities["connectors"].size()) && + (!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || + Controller::Storage["config"]["protocols"].size() < 1)){ + // create protocols + jsonForEach(Controller::capabilities["connectors"], it){ + if (!it->isMember("required")){ + JSON::Value newProtocol; + newProtocol["connector"] = it.key(); + Controller::Storage["config"]["protocols"].append(newProtocol); + } + } + } + }break; + } + } + } + // check for protocols + if ((Controller::capabilities["connectors"].size()) && + (!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || + Controller::Storage["config"]["protocols"].size() < 1)){ + std::string in_string = ""; + while (yna(in_string) == 'x' && Controller::conf.is_active){ + std::cout << "Protocols not set, do you want to enable default protocols? (y)es, (n)o, (a)bort: "; + std::cout.flush(); + std::getline(std::cin, in_string); + if (yna(in_string) == 'y'){ + // create protocols + jsonForEach(Controller::capabilities["connectors"], it){ + if (!it->isMember("required")){ + JSON::Value newProtocol; + newProtocol["connector"] = it.key(); + Controller::Storage["config"]["protocols"].append(newProtocol); + } + } + }else if (yna(in_string) == 'a'){ + // abort controller startup + return false; + } + } + } + return true; +} + ///\brief The main loop for the controller. int main_loop(int argc, char **argv){ { @@ -167,11 +276,6 @@ int main_loop(int argc, char **argv){ Controller::conf.addOption("port", stored_port); Controller::conf.addOption("interface", stored_interface); Controller::conf.addOption("username", stored_user); - Controller::conf.addOption( - "maxconnsperip", - JSON::fromString("{\"long\":\"maxconnsperip\", \"short\":\"M\", \"arg\":\"integer\" " - "\"default\":0, \"help\":\"Max simultaneous sessions per unique IP address. " - "Only enforced if the USER_NEW trigger is in use.\"}")); Controller::conf.addOption( "account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" " "\"default\":\"\", \"help\":\"A username:password string to " @@ -234,8 +338,6 @@ int main_loop(int argc, char **argv){ << "!----" APPNAME " Started at " << buffer << " ----!" << std::endl; } } - // reload config from config file - Controller::Storage = JSON::fromFile(Controller::conf.getString("configFile")); {// spawn thread that reads stderr of process std::string logPipe = Util::getTmpFolder() + "MstLog"; @@ -278,81 +380,8 @@ int main_loop(int argc, char **argv){ } setenv("MIST_CONTROL", "1", 0); // Signal in the environment that the controller handles all children } - - if (Controller::Storage.isMember("config_split")){ - jsonForEach(Controller::Storage["config_split"], cs){ - if (cs->isString()){ - JSON::Value tmpConf = JSON::fromFile(cs->asStringRef()); - if (tmpConf.isMember(cs.key())){ - INFO_MSG("Loading '%s' section of config from file %s", cs.key().c_str(), cs->asStringRef().c_str()); - Controller::Storage[cs.key()] = tmpConf[cs.key()]; - }else{ - WARN_MSG("There is no '%s' section in file %s; skipping load", cs.key().c_str(), cs->asStringRef().c_str()); - } - } - } - } - // Set default delay before retry - if (!Controller::Storage.isMember("push_settings")){ - Controller::Storage["push_settings"]["wait"] = 3; - Controller::Storage["push_settings"]["maxspeed"] = 0; - } - if (Controller::conf.getOption("debug", true).size() > 1){ - Controller::Storage["config"]["debug"] = Controller::conf.getInteger("debug"); - } - if (Controller::Storage.isMember("config") && Controller::Storage["config"].isMember("debug") && - Controller::Storage["config"]["debug"].isInt()){ - Util::printDebugLevel = Controller::Storage["config"]["debug"].asInt(); - } - // check for port, interface and username in arguments - // if they are not there, take them from config file, if there - if (Controller::Storage["config"]["controller"]["port"]){ - Controller::conf.getOption("port", true)[0u] = - Controller::Storage["config"]["controller"]["port"]; - } - if (Controller::Storage["config"]["controller"]["interface"]){ - Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"]; - } - if (Controller::Storage["config"]["controller"]["username"]){ - Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"]; - } - if (Controller::Storage["config"]["controller"].isMember("prometheus")){ - if (Controller::Storage["config"]["controller"]["prometheus"]){ - Controller::Storage["config"]["prometheus"] = - Controller::Storage["config"]["controller"]["prometheus"]; - } - Controller::Storage["config"]["controller"].removeMember("prometheus"); - } - if (Controller::Storage["config"]["prometheus"]){ - Controller::conf.getOption("prometheus", true)[0u] = - Controller::Storage["config"]["prometheus"]; - } - if (Controller::Storage["config"].isMember("accesslog")){ - Controller::conf.getOption("accesslog", true)[0u] = Controller::Storage["config"]["accesslog"]; - } - Controller::Storage["config"]["prometheus"] = Controller::conf.getString("prometheus"); - Controller::Storage["config"]["accesslog"] = Controller::conf.getString("accesslog"); - Controller::normalizeTrustedProxies(Controller::Storage["config"]["trustedproxy"]); - if (!Controller::Storage["config"]["sessionViewerMode"]){ - Controller::Storage["config"]["sessionViewerMode"] = SESS_BUNDLE_DEFAULT_VIEWER; - } - if (!Controller::Storage["config"]["sessionInputMode"]){ - Controller::Storage["config"]["sessionInputMode"] = SESS_BUNDLE_DEFAULT_OTHER; - } - if (!Controller::Storage["config"]["sessionOutputMode"]){ - Controller::Storage["config"]["sessionOutputMode"] = SESS_BUNDLE_DEFAULT_OTHER; - } - if (!Controller::Storage["config"]["sessionUnspecifiedMode"]){ - Controller::Storage["config"]["sessionUnspecifiedMode"] = 0; - } - if (!Controller::Storage["config"]["sessionStreamInfoMode"]){ - Controller::Storage["config"]["sessionStreamInfoMode"] = SESS_DEFAULT_STREAM_INFO_MODE; - } - if (!Controller::Storage["config"].isMember("tknMode")){ - Controller::Storage["config"]["tknMode"] = SESS_TKN_DEFAULT_MODE; - } - Controller::prometheus = Controller::Storage["config"]["prometheus"].asStringRef(); - Controller::accesslog = Controller::Storage["config"]["accesslog"].asStringRef(); + + Controller::readConfigFromDisk(); Controller::writeConfig(); if (!Controller::conf.is_active){return 0;} Controller::checkAvailProtocols(); @@ -435,74 +464,9 @@ int main_loop(int argc, char **argv){ } #endif - // if a terminal is connected and we're not logging to file + // if a terminal is connected, check for first time setup if (Controller::isTerminal){ - // check for username - if (!Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){ - std::string in_string = ""; - while (yna(in_string) == 'x' && Controller::conf.is_active){ - std::cout << "Account not set, do you want to create an account? (y)es, (n)o, (a)bort: "; - std::cout.flush(); - std::getline(std::cin, in_string); - switch (yna(in_string)){ - case 'y':{ - // create account - std::string usr_string = ""; - while (!(Controller::Storage.isMember("account") && Controller::Storage["account"].size() > 0) && - Controller::conf.is_active){ - std::cout << "Please type in the username, a colon and a password in the following " - "format; username:password" - << std::endl - << ": "; - std::cout.flush(); - std::getline(std::cin, usr_string); - createAccount(usr_string); - } - }break; - case 'a': return 0; // abort bootup - case 't':{ - createAccount("test:test"); - if ((Controller::capabilities["connectors"].size()) && - (!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || - Controller::Storage["config"]["protocols"].size() < 1)){ - // create protocols - jsonForEach(Controller::capabilities["connectors"], it){ - if (!it->isMember("required")){ - JSON::Value newProtocol; - newProtocol["connector"] = it.key(); - Controller::Storage["config"]["protocols"].append(newProtocol); - } - } - } - }break; - } - } - } - // check for protocols - if ((Controller::capabilities["connectors"].size()) && - (!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || - Controller::Storage["config"]["protocols"].size() < 1)){ - std::string in_string = ""; - while (yna(in_string) == 'x' && Controller::conf.is_active){ - std::cout << "Protocols not set, do you want to enable default protocols? (y)es, (n)o, " - "(a)bort: "; - std::cout.flush(); - std::getline(std::cin, in_string); - if (yna(in_string) == 'y'){ - // create protocols - jsonForEach(Controller::capabilities["connectors"], it){ - if (!it->isMember("required")){ - JSON::Value newProtocol; - newProtocol["connector"] = it.key(); - Controller::Storage["config"]["protocols"].append(newProtocol); - } - } - }else if (yna(in_string) == 'a'){ - // abort controller startup - return 0; - } - } - } + if (!interactiveFirstTimeSetup()){return 0;} } // Check if we have a usable server, if not, print messages with helpful hints @@ -530,39 +494,6 @@ int main_loop(int argc, char **argv){ } } - // Upgrade old configurations - { - bool foundCMAF = false; - bool edit = false; - JSON::Value newVal; - jsonForEach(Controller::Storage["config"]["protocols"], it){ - if ((*it)["connector"].asStringRef() == "HSS"){ - edit = true; - continue; - } - if ((*it)["connector"].asStringRef() == "DASH"){ - edit = true; - continue; - } - - if ((*it)["connector"].asStringRef() == "SRT"){ - edit = true; - JSON::Value newSubRip = *it; - newSubRip["connector"] = "SubRip"; - newVal.append(newSubRip); - continue; - } - - if ((*it)["connector"].asStringRef() == "CMAF"){foundCMAF = true;} - newVal.append(*it); - } - if (edit && !foundCMAF){newVal.append(JSON::fromString("{\"connector\":\"CMAF\"}"));} - if (edit){ - Controller::Storage["config"]["protocols"] = newVal; - Controller::Log("CONF", "Translated protocols to new versions"); - } - } - // Generate instanceId once per boot. if (Controller::instanceId == ""){ srand(mix(clock(), time(0), getpid())); @@ -648,7 +579,7 @@ int main_loop(int argc, char **argv){ /*LTS-END*/ // write config tthread::lock_guard guard(Controller::logMutex); - Controller::writeConfigToDisk(); + Controller::writeConfigToDisk(true); // stop all child processes Util::Procs::StopAll(); // give everything some time to print messages @@ -719,3 +650,4 @@ int main(int argc, char **argv){ } return 0; } + diff --git a/src/controller/controller_api.cpp b/src/controller/controller_api.cpp index c167b9c1..e96be709 100644 --- a/src/controller/controller_api.cpp +++ b/src/controller/controller_api.cpp @@ -552,6 +552,11 @@ void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){ Response["config_backup"].assignFrom(Controller::Storage, skip); } + if (Request.isMember("config_reload")){ + INFO_MSG("Reloading configuration from disk on request"); + Controller::readConfigFromDisk(); + } + if (Request.isMember("config_restore")){ std::set skip; skip.insert("log"); @@ -1222,11 +1227,10 @@ void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){ } Controller::writeConfig(); - Controller::configChanged = false; if (Request.isMember("save")){ Controller::Log("CONF", "Writing config to file on request through API"); - Controller::writeConfigToDisk(); + Controller::writeConfigToDisk(true); } } diff --git a/src/controller/controller_storage.cpp b/src/controller/controller_storage.cpp index c8422c43..1b22829e 100644 --- a/src/controller/controller_storage.cpp +++ b/src/controller/controller_storage.cpp @@ -22,7 +22,8 @@ namespace Controller{ tthread::mutex configMutex; tthread::mutex logMutex; uint64_t logCounter = 0; - bool configChanged = false; + uint64_t lastConfigChange = 0; + uint64_t lastConfigWrite = 0; bool isTerminal = false; bool isColorized = false; uint32_t maxLogsRecs = 0; @@ -36,6 +37,10 @@ namespace Controller{ Util::RelAccX *rlxStrm = 0; uint64_t systemBoot = Util::unixMS() - Util::bootMS(); + JSON::Value lastConfigWriteAttempt; + JSON::Value lastConfigSeen; + std::map lastConfigWritten; + Util::RelAccX *logAccessor(){return rlxLogs;} Util::RelAccX *accesslogAccessor(){return rlxAccs;} @@ -238,37 +243,193 @@ namespace Controller{ Util::logParser((long long)err, fileno(stdout), Controller::isColorized, &Log); } - /// Writes the current config to the location set in the configFile setting. - /// On error, prints an error-level message and the config to stdout. - void writeConfigToDisk(){ - JSON::Value tmp; + void getConfigAsWritten(JSON::Value & conf){ std::set skip; skip.insert("log"); skip.insert("online"); skip.insert("error"); - tmp.assignFrom(Controller::Storage, skip); + conf.assignFrom(Controller::Storage, skip); + } + + /// Writes the current config to the location set in the configFile setting. + /// On error, prints an error-level message and the config to stdout. + void writeConfigToDisk(bool forceWrite){ + bool success = true; + JSON::Value tmp; + getConfigAsWritten(tmp); + + // We keep an extra copy temporarily, since we want to keep the "full" config around for comparisons + JSON::Value mainConfig = tmp; if (Controller::Storage.isMember("config_split")){ jsonForEach(Controller::Storage["config_split"], cs){ if (cs->isString() && tmp.isMember(cs.key())){ + // Only (attempt to) write if there was a change since last write success + if (!forceWrite && lastConfigWritten[cs.key()] == tmp[cs.key()]){ + if (cs.key() != "config_split"){mainConfig.removeMember(cs.key());} + continue; + } JSON::Value tmpConf = JSON::fromFile(cs->asStringRef()); tmpConf[cs.key()] = tmp[cs.key()]; + // Attempt to write this section to the given file if (!Controller::WriteFile(cs->asStringRef(), tmpConf.toString())){ - ERROR_MSG("Error writing config.%s to %s", cs.key().c_str(), cs->asStringRef().c_str()); - std::cout << "**config." << cs.key() <<"**" << std::endl; - std::cout << tmp[cs.key()].toString() << std::endl; - std::cout << "**End config." << cs.key() << "**" << std::endl; + success = false; + // Only print the error + config data if this is new data since the last write attempt + if (tmp[cs.key()] != lastConfigWriteAttempt[cs.key()]){ + ERROR_MSG("Error writing config.%s to %s", cs.key().c_str(), cs->asStringRef().c_str()); + std::cout << "**config." << cs.key() <<"**" << std::endl; + std::cout << tmp[cs.key()].toString() << std::endl; + std::cout << "**End config." << cs.key() << "**" << std::endl; + } + }else{ + // Log the successfully written data + lastConfigWritten[cs.key()] = tmp[cs.key()]; } - if (cs.key() != "config_split"){tmp.removeMember(cs.key());} + // Remove this section from the to-be-written main config + if (cs.key() != "config_split"){mainConfig.removeMember(cs.key());} } } } - if (!Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){ - ERROR_MSG("Error writing config to %s", Controller::conf.getString("configFile").c_str()); - std::cout << "**Config**" << std::endl; - std::cout << tmp.toString() << std::endl; - std::cout << "**End config**" << std::endl; + + // Only (attempt to) write if there was a change since last write success + if (forceWrite || lastConfigWritten[""] != mainConfig){ + // Attempt to write this section to the given file + if (!Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){ + success = false; + // Only print the error + config data if this is new data since the last write attempt + if (tmp != lastConfigWriteAttempt){ + ERROR_MSG("Error writing config to %s", Controller::conf.getString("configFile").c_str()); + std::cout << "**Config**" << std::endl; + std::cout << mainConfig.toString() << std::endl; + std::cout << "**End config**" << std::endl; + } + }else{ + lastConfigWritten[""] = mainConfig; + } } + + if (success){ + INFO_MSG("Wrote updated configuration to disk"); + lastConfigWrite = Util::epoch(); + } + lastConfigWriteAttempt = tmp; + } + + void readConfigFromDisk(){ + // reload config from config file + Controller::Storage = JSON::fromFile(Controller::conf.getString("configFile")); + + if (Controller::Storage.isMember("config_split")){ + jsonForEach(Controller::Storage["config_split"], cs){ + if (cs->isString()){ + JSON::Value tmpConf = JSON::fromFile(cs->asStringRef()); + if (tmpConf.isMember(cs.key())){ + INFO_MSG("Loading '%s' section of config from file %s", cs.key().c_str(), cs->asStringRef().c_str()); + Controller::Storage[cs.key()] = tmpConf[cs.key()]; + }else{ + WARN_MSG("There is no '%s' section in file %s; skipping load", cs.key().c_str(), cs->asStringRef().c_str()); + } + } + } + } + // Set default delay before retry + if (!Controller::Storage.isMember("push_settings")){ + Controller::Storage["push_settings"]["wait"] = 3; + Controller::Storage["push_settings"]["maxspeed"] = 0; + } + if (Controller::conf.getOption("debug", true).size() > 1){ + Controller::Storage["config"]["debug"] = Controller::conf.getInteger("debug"); + } + if (Controller::Storage.isMember("config") && Controller::Storage["config"].isMember("debug") && + Controller::Storage["config"]["debug"].isInt()){ + Util::printDebugLevel = Controller::Storage["config"]["debug"].asInt(); + } + // check for port, interface and username in arguments + // if they are not there, take them from config file, if there + if (Controller::Storage["config"]["controller"]["port"]){ + Controller::conf.getOption("port", true)[0u] = + Controller::Storage["config"]["controller"]["port"]; + } + if (Controller::Storage["config"]["controller"]["interface"]){ + Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"]; + } + if (Controller::Storage["config"]["controller"]["username"]){ + Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"]; + } + if (Controller::Storage["config"]["controller"].isMember("prometheus")){ + if (Controller::Storage["config"]["controller"]["prometheus"]){ + Controller::Storage["config"]["prometheus"] = + Controller::Storage["config"]["controller"]["prometheus"]; + } + Controller::Storage["config"]["controller"].removeMember("prometheus"); + } + if (Controller::Storage["config"]["prometheus"]){ + Controller::conf.getOption("prometheus", true)[0u] = + Controller::Storage["config"]["prometheus"]; + } + if (Controller::Storage["config"].isMember("accesslog")){ + Controller::conf.getOption("accesslog", true)[0u] = Controller::Storage["config"]["accesslog"]; + } + Controller::Storage["config"]["prometheus"] = Controller::conf.getString("prometheus"); + Controller::Storage["config"]["accesslog"] = Controller::conf.getString("accesslog"); + Controller::normalizeTrustedProxies(Controller::Storage["config"]["trustedproxy"]); + if (!Controller::Storage["config"]["sessionViewerMode"]){ + Controller::Storage["config"]["sessionViewerMode"] = SESS_BUNDLE_DEFAULT_VIEWER; + } + if (!Controller::Storage["config"]["sessionInputMode"]){ + Controller::Storage["config"]["sessionInputMode"] = SESS_BUNDLE_DEFAULT_OTHER; + } + if (!Controller::Storage["config"]["sessionOutputMode"]){ + Controller::Storage["config"]["sessionOutputMode"] = SESS_BUNDLE_DEFAULT_OTHER; + } + if (!Controller::Storage["config"]["sessionUnspecifiedMode"]){ + Controller::Storage["config"]["sessionUnspecifiedMode"] = 0; + } + if (!Controller::Storage["config"]["sessionStreamInfoMode"]){ + Controller::Storage["config"]["sessionStreamInfoMode"] = SESS_DEFAULT_STREAM_INFO_MODE; + } + if (!Controller::Storage["config"].isMember("tknMode")){ + Controller::Storage["config"]["tknMode"] = SESS_TKN_DEFAULT_MODE; + } + Controller::prometheus = Controller::Storage["config"]["prometheus"].asStringRef(); + Controller::accesslog = Controller::Storage["config"]["accesslog"].asStringRef(); + + // Upgrade old configurations + { + bool foundCMAF = false; + bool edit = false; + JSON::Value newVal; + jsonForEach(Controller::Storage["config"]["protocols"], it){ + if ((*it)["connector"].asStringRef() == "HSS"){ + edit = true; + continue; + } + if ((*it)["connector"].asStringRef() == "DASH"){ + edit = true; + continue; + } + + if ((*it)["connector"].asStringRef() == "SRT"){ + edit = true; + JSON::Value newSubRip = *it; + newSubRip["connector"] = "SubRip"; + newVal.append(newSubRip); + continue; + } + + if ((*it)["connector"].asStringRef() == "CMAF"){foundCMAF = true;} + newVal.append(*it); + } + if (edit && !foundCMAF){newVal.append(JSON::fromString("{\"connector\":\"CMAF\"}"));} + if (edit){ + Controller::Storage["config"]["protocols"] = newVal; + Controller::Log("CONF", "Translated protocols to new versions"); + } + } + Controller::lastConfigChange = Controller::lastConfigWrite = Util::epoch(); + Controller::lastConfigWriteAttempt.null(); + getConfigAsWritten(Controller::lastConfigWriteAttempt); + lastConfigSeen = lastConfigWriteAttempt; } void writeCapabilities(){ diff --git a/src/controller/controller_storage.h b/src/controller/controller_storage.h index 833fe20f..a03eaaa6 100644 --- a/src/controller/controller_storage.h +++ b/src/controller/controller_storage.h @@ -5,7 +5,7 @@ #include namespace Controller{ - extern std::string instanceId; ///< global storage of instanceId (previously uniqID) is set in controller.cpp + extern std::string instanceId; ///< global storage of instanceId (previously uniqID) is set in controller.cpp extern std::string prometheus; ///< Prometheus access string extern std::string accesslog; ///< Where to write the access log extern std::string udpApiBindAddr; ///< Bound address where the UDP API listens @@ -13,11 +13,14 @@ namespace Controller{ extern JSON::Value Storage; ///< Global storage of data. extern tthread::mutex logMutex; ///< Mutex for log thread. extern tthread::mutex configMutex; ///< Mutex for server config access. - extern bool configChanged; ///< Bool that indicates config must be written to SHM. extern bool isTerminal; ///< True if connected to a terminal and not a log file. extern bool isColorized; ///< True if we colorize the output extern uint64_t logCounter; ///< Count of logged messages since boot extern uint64_t systemBoot; ///< Unix time in milliseconds of system boot + extern uint64_t lastConfigChange; ///< Unix time in seconds of last configuration change + extern uint64_t lastConfigWrite; ///< Unix time in seconds of last time configuration was written to disk + extern JSON::Value lastConfigWriteAttempt; ///< Contents of last attempted config write + extern JSON::Value lastConfigSeen; ///< Contents of config last time we looked at it. Used to check for changes. Util::RelAccX *logAccessor(); Util::RelAccX *accesslogAccessor(); @@ -34,7 +37,10 @@ namespace Controller{ /// Write contents to Filename. bool WriteFile(std::string Filename, std::string contents); - void writeConfigToDisk(); + + void getConfigAsWritten(JSON::Value & conf); + void writeConfigToDisk(bool forceWrite = false); + void readConfigFromDisk(); void handleMsg(void *err); void initState();