diff --git a/doc/README.md b/doc/README.md index 73313cc..eeb756c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -140,6 +140,15 @@ Equipments should be prefixed as `[equipment-...]`. Consumers should be prefixed as `[consumer-...]`. General settings are defined in section `[readout]`. +Section names ending with `-*` can be used to define default parameters. They are applied to all section with similar names. Existing key-value pairs are not overwritten, but are defined according to defaults if they don't exist. For example, it is possible to define the TFperiod for all equipments by adding a section named `[equipment-*]` with `TFperiod=32`. + +Values can be symbolic links to a value stored in another configuration file. The syntax is: @LINK,URI,entryPoint,path +For example: @LINK,file:/local/readout-test-config-link1.cfg,,bank.size +Parameters are similar to the command-line arguments of o2-readout-exe, see ```Usage``` below. +Files are cached, i.e. the corresponding configuration tree is loaded only once if several values use a link to the same URI/entryPoint. +Links substitutions are done at the end of the configuration tree aggregation (sections merging, etc). +It is done recursively (up to 5 iterations, after which it fails to avoid circular dependencies). + Comments can be added by starting a line with the # sign. Inline comments (# later in the line) are not accepted. Documented example files are provided with the source code and distribution. diff --git a/doc/releaseNotes.md b/doc/releaseNotes.md index 6153df5..2a6331a 100644 --- a/doc/releaseNotes.md +++ b/doc/releaseNotes.md @@ -628,3 +628,7 @@ This file describes the main feature changes for each readout.exe released versi ## v2.24.0 - 18/06/2024 - Updated configuration parameters: - equipment-rorc-*: added parameters firmwareVersionsDenied and firmwareVersionsAllowed, to enforce the check of firmware for specific versions. By default, v3.10.0 (e4a5a46e) is denied and all other allowed: it is the default version in CRU flash and should be updated to a more recent one at boot time. + +## v2.25.0 - 25/06/2024 +- Updated configuration parameters: + - Values can be a link to a value stored in another file. diff --git a/src/ReadoutVersion.h b/src/ReadoutVersion.h index 765f344..a6e4173 100644 --- a/src/ReadoutVersion.h +++ b/src/ReadoutVersion.h @@ -9,5 +9,5 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#define READOUT_VERSION "2.24.0" +#define READOUT_VERSION "2.25.0" diff --git a/src/mainReadout.cxx b/src/mainReadout.cxx index 8a5926e..002e3d9 100644 --- a/src/mainReadout.cxx +++ b/src/mainReadout.cxx @@ -932,6 +932,98 @@ int Readout::_configure(const boost::property_tree::ptree& properties) pos = cfg.get().erase(pos); } + // resolve "symlinks" + int cfgLinksErrors = 0; + struct ConfigCache { + std::string URI; + std::string EntryPoint; + std::unique_ptr cfg; + }; + std::vector cfgCache; + int nSubstitutions = 0; + std::function resolveLinks; + resolveLinks = [&resolveLinks, &cfgLinksErrors, &cfgCache, &loadConfig, &nSubstitutions](boost::property_tree::ptree &pt, const std::string &key) -> void { + if (pt.empty()) { + std::string value = pt.data(); + const std::string keywordLink = "@LINK"; + if (value.compare(0, keywordLink.length(), keywordLink) == 0) { + // this is a symlink + // extract reference + // syntax: @LINK,URI,EntryPoint,Path + std::vector linkArgs; + getListFromString(value, linkArgs, ','); + if (linkArgs.size() != 4) { + theLog.log(LogErrorSupport_(3102), "Failed to parse link: %s = %s", key.c_str(), value.c_str()); + cfgLinksErrors++; + return; + } + const char* cfgLinkUri = linkArgs[1].c_str(); + const char* cfgLinkEntryPoint = linkArgs[2].c_str(); + const char* cfgLinkPath = linkArgs[3].c_str(); + // search for file in cache + unsigned int ix = 0; + for(;ix < cfgCache.size(); ix++) { + if ((cfgCache[ix].URI == cfgLinkUri) && (cfgCache[ix].EntryPoint == cfgLinkEntryPoint)) { + break; + } + } + if (ix == cfgCache.size()) { + // no match in cache, add it + try { + auto cfg = std::make_unique(); + if (cfg==nullptr) { + throw __LINE__; + } + if (loadConfig(cfgLinkUri, cfgLinkEntryPoint, *cfg)) { + throw __LINE__; + } + cfgCache.push_back({cfgLinkUri, cfgLinkEntryPoint, std::move(cfg)}); + } + catch(...) { + theLog.log(LogErrorSupport_(3102), "Failed to load linked configuration %s %s", cfgLinkUri, cfgLinkEntryPoint); + cfgLinksErrors++; + return; + } + theLog.log(LogInfoSupport, "Reading linked configuration from %s %s", cfgLinkUri, cfgLinkEntryPoint); + } + // at this stage we now have a valid config in cache at index ix + // get value from linked config + std::string linkValue; + if (cfgCache[ix].cfg->getOptionalValue(cfgLinkPath, linkValue)) { + theLog.log(LogErrorSupport_(3102), "Failed to get link value: %s = %s", key.c_str(), value.c_str()); + cfgLinksErrors++; + return; + } + theLog.log(LogInfoDevel_(3002), "Link substituted : %s = %s -> %s", key.c_str(), value.c_str(), linkValue.c_str()); + pt.data() = linkValue; + nSubstitutions++; + } + return; + } + for (boost::property_tree::ptree::iterator pos = pt.begin(); pos != pt.end();++pos) { + //printf("%s\n", pos->first.c_str()); + resolveLinks(pos->second, pos->first.c_str()); + } + }; + int maxLoops = 5; + for (int i = 0; i <= maxLoops; i++) { + if (i == maxLoops) { + theLog.log(LogErrorSupport_(3100), "Links not fully resolved after %d iterations, there might be some circular dependencies in the configuration", maxLoops); + cfgLinksErrors++; + break; + } + nSubstitutions = 0; + resolveLinks(cfg.get(),""); + if (nSubstitutions == 0) { + break; + } + } + if (cfgLinksErrors) { + theLog.log(LogErrorSupport_(3100), "Some links in the configuration could not be resolved"); + return -1; + } + + // extract optional configuration parameters // configuration parameter: | readout | customCommands | string | | List of key=value pairs defining some custom shell commands to be executed at before/after state change commands. | if (customCommandsShellPid) {