1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #include "srvcfg_manager.hpp" 17 18 #include <boost/algorithm/string/replace.hpp> 19 #include <cereal/archives/json.hpp> 20 #include <cereal/types/tuple.hpp> 21 #include <cereal/types/unordered_map.hpp> 22 #include <sdbusplus/bus/match.hpp> 23 24 #include <filesystem> 25 #include <fstream> 26 27 std::unique_ptr<boost::asio::steady_timer> timer = nullptr; 28 std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr; 29 std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>> 30 srvMgrObjects; 31 static bool unitQueryStarted = false; 32 33 static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json"; 34 35 // Base service name list. All instance of these services and 36 // units(service/socket) will be managed by this daemon. 37 static std::array<std::string, 6> serviceNames = { 38 "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs", 39 "start-ipkvm", "obmc-console", "dropbear"}; 40 41 enum class UnitType 42 { 43 service, 44 socket, 45 target, 46 device, 47 invalid 48 }; 49 50 using MonitorListMap = 51 std::unordered_map<std::string, std::tuple<std::string, std::string, 52 std::string, std::string>>; 53 MonitorListMap unitsToMonitor; 54 55 enum class monitorElement 56 { 57 unitName, 58 instanceName, 59 serviceObjPath, 60 socketObjPath 61 }; 62 63 std::tuple<std::string, UnitType, std::string> 64 getUnitNameTypeAndInstance(const std::string& fullUnitName) 65 { 66 UnitType type = UnitType::invalid; 67 std::string instanceName; 68 std::string unitName; 69 // get service type 70 auto typePos = fullUnitName.rfind("."); 71 if (typePos != std::string::npos) 72 { 73 const auto& typeStr = fullUnitName.substr(typePos + 1); 74 // Ignore types other than service and socket 75 if (typeStr == "service") 76 { 77 type = UnitType::service; 78 } 79 else if (typeStr == "socket") 80 { 81 type = UnitType::socket; 82 } 83 // get instance name if available 84 auto instancePos = fullUnitName.rfind("@"); 85 if (instancePos != std::string::npos) 86 { 87 instanceName = 88 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1); 89 unitName = fullUnitName.substr(0, instancePos); 90 } 91 else 92 { 93 unitName = fullUnitName.substr(0, typePos); 94 } 95 } 96 return std::make_tuple(unitName, type, instanceName); 97 } 98 99 static inline void 100 handleListUnitsResponse(sdbusplus::asio::object_server& server, 101 std::shared_ptr<sdbusplus::asio::connection>& conn, 102 boost::system::error_code /*ec*/, 103 const std::vector<ListUnitsType>& listUnits) 104 { 105 // Loop through all units, and mark all units, which has to be 106 // managed, irrespective of instance name. 107 for (const auto& unit : listUnits) 108 { 109 const auto& fullUnitName = 110 std::get<static_cast<int>(ListUnitElements::name)>(unit); 111 auto [unitName, type, instanceName] = 112 getUnitNameTypeAndInstance(fullUnitName); 113 if (std::find(serviceNames.begin(), serviceNames.end(), unitName) != 114 serviceNames.end()) 115 { 116 std::string instantiatedUnitName = 117 unitName + addInstanceName(instanceName, "_40"); 118 boost::replace_all(instantiatedUnitName, "-", "_2d"); 119 const sdbusplus::message::object_path& objectPath = 120 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit); 121 // Group the service & socket units togther.. Same services 122 // are managed together. 123 auto it = unitsToMonitor.find(instantiatedUnitName); 124 if (it != unitsToMonitor.end()) 125 { 126 auto& value = it->second; 127 if (type == UnitType::service) 128 { 129 std::get<static_cast<int>(monitorElement::unitName)>( 130 value) = unitName; 131 std::get<static_cast<int>(monitorElement::instanceName)>( 132 value) = instanceName; 133 std::get<static_cast<int>(monitorElement::serviceObjPath)>( 134 value) = objectPath; 135 } 136 else if (type == UnitType::socket) 137 { 138 std::get<static_cast<int>(monitorElement::socketObjPath)>( 139 value) = objectPath; 140 } 141 } 142 if (type == UnitType::service) 143 { 144 unitsToMonitor.emplace(instantiatedUnitName, 145 std::make_tuple(unitName, instanceName, 146 objectPath.str, "")); 147 } 148 else if (type == UnitType::socket) 149 { 150 unitsToMonitor.emplace( 151 instantiatedUnitName, 152 std::make_tuple("", "", "", objectPath.str)); 153 } 154 } 155 } 156 157 bool updateRequired = false; 158 bool jsonExist = std::filesystem::exists(srvCfgMgrFile); 159 if (jsonExist) 160 { 161 std::ifstream file(srvCfgMgrFile); 162 cereal::JSONInputArchive archive(file); 163 MonitorListMap savedMonitorList; 164 archive(savedMonitorList); 165 166 // compare the unit list read from systemd1 and the save list. 167 MonitorListMap diffMap; 168 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor), 169 begin(savedMonitorList), end(savedMonitorList), 170 std::inserter(diffMap, begin(diffMap))); 171 for (auto& unitIt : diffMap) 172 { 173 auto it = savedMonitorList.find(unitIt.first); 174 if (it == savedMonitorList.end()) 175 { 176 savedMonitorList.insert(unitIt); 177 updateRequired = true; 178 } 179 } 180 unitsToMonitor = savedMonitorList; 181 } 182 if (!jsonExist || updateRequired) 183 { 184 std::ofstream file(srvCfgMgrFile); 185 cereal::JSONOutputArchive archive(file); 186 archive(CEREAL_NVP(unitsToMonitor)); 187 } 188 189 // create objects for needed services 190 for (auto& it : unitsToMonitor) 191 { 192 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) + 193 "/" + it.first); 194 std::string instanciatedUnitName = 195 std::get<static_cast<int>(monitorElement::unitName)>(it.second) + 196 addInstanceName( 197 std::get<static_cast<int>(monitorElement::instanceName)>( 198 it.second), 199 "@"); 200 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>( 201 server, conn, objPath, 202 std::get<static_cast<int>(monitorElement::unitName)>(it.second), 203 std::get<static_cast<int>(monitorElement::instanceName)>(it.second), 204 std::get<static_cast<int>(monitorElement::serviceObjPath)>( 205 it.second), 206 std::get<static_cast<int>(monitorElement::socketObjPath)>( 207 it.second)); 208 srvMgrObjects.emplace( 209 std::make_pair(std::move(objPath), std::move(srvCfgObj))); 210 } 211 } 212 213 void init(sdbusplus::asio::object_server& server, 214 std::shared_ptr<sdbusplus::asio::connection>& conn) 215 { 216 // Go through all systemd units, and dynamically detect and manage 217 // the service daemons 218 conn->async_method_call( 219 [&server, &conn](boost::system::error_code ec, 220 const std::vector<ListUnitsType>& listUnits) { 221 if (ec) 222 { 223 lg2::error("async_method_call error: ListUnits failed: {EC}", 224 "EC", ec.value()); 225 return; 226 } 227 handleListUnitsResponse(server, conn, ec, listUnits); 228 }, 229 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits"); 230 } 231 232 void checkAndInit(sdbusplus::asio::object_server& server, 233 std::shared_ptr<sdbusplus::asio::connection>& conn) 234 { 235 // Check whether systemd completed all the loading before initializing 236 conn->async_method_call( 237 [&server, &conn](boost::system::error_code ec, 238 const std::variant<uint64_t>& value) { 239 if (ec) 240 { 241 lg2::error("async_method_call error: ListUnits failed: {EC}", 242 "EC", ec.value()); 243 return; 244 } 245 if (std::get<uint64_t>(value)) 246 { 247 if (!unitQueryStarted) 248 { 249 unitQueryStarted = true; 250 init(server, conn); 251 } 252 } 253 else 254 { 255 // FIX-ME: Latest up-stream sync caused issue in receiving 256 // StartupFinished signal. Unable to get StartupFinished signal 257 // from systemd1 hence using poll method too, to trigger it 258 // properly. 259 constexpr size_t pollTimeout = 10; // seconds 260 initTimer->expires_after(std::chrono::seconds(pollTimeout)); 261 initTimer->async_wait([&server, &conn]( 262 const boost::system::error_code& ec) { 263 if (ec == boost::asio::error::operation_aborted) 264 { 265 // Timer reset. 266 return; 267 } 268 if (ec) 269 { 270 lg2::error( 271 "service config mgr - init - async wait error: {EC}", 272 "EC", ec.value()); 273 return; 274 } 275 checkAndInit(server, conn); 276 }); 277 } 278 }, 279 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf, 280 "FinishTimestamp"); 281 } 282 283 int main() 284 { 285 boost::asio::io_service io; 286 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 287 timer = std::make_unique<boost::asio::steady_timer>(io); 288 initTimer = std::make_unique<boost::asio::steady_timer>(io); 289 conn->request_name(phosphor::service::serviceConfigSrvName); 290 auto server = sdbusplus::asio::object_server(conn, true); 291 server.add_manager(phosphor::service::srcCfgMgrBasePath); 292 // Initialize the objects after systemd indicated startup finished. 293 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>( 294 static_cast<sdbusplus::bus::bus&>(*conn), 295 "type='signal'," 296 "member='StartupFinished',path='/org/freedesktop/systemd1'," 297 "interface='org.freedesktop.systemd1.Manager'", 298 [&server, &conn](sdbusplus::message::message& /*msg*/) { 299 if (!unitQueryStarted) 300 { 301 unitQueryStarted = true; 302 init(server, conn); 303 } 304 }); 305 // this will make sure to initialize the objects, when daemon is 306 // restarted. 307 checkAndInit(server, conn); 308 309 io.run(); 310 311 return 0; 312 } 313