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 static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad"; 35 36 // Base service name list. All instance of these services and 37 // units(service/socket) will be managed by this daemon. 38 static std::array<std::string, 6> serviceNames = { 39 "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs", 40 "start-ipkvm", "obmc-console", "dropbear"}; 41 42 enum class UnitType 43 { 44 service, 45 socket, 46 target, 47 device, 48 invalid 49 }; 50 51 using MonitorListMap = 52 std::unordered_map<std::string, std::tuple<std::string, std::string, 53 std::string, std::string>>; 54 MonitorListMap unitsToMonitor; 55 56 enum class monitorElement 57 { 58 unitName, 59 instanceName, 60 serviceObjPath, 61 socketObjPath 62 }; 63 64 std::tuple<std::string, UnitType, std::string> 65 getUnitNameTypeAndInstance(const std::string& fullUnitName) 66 { 67 UnitType type = UnitType::invalid; 68 std::string instanceName; 69 std::string unitName; 70 // get service type 71 auto typePos = fullUnitName.rfind("."); 72 if (typePos != std::string::npos) 73 { 74 const auto& typeStr = fullUnitName.substr(typePos + 1); 75 // Ignore types other than service and socket 76 if (typeStr == "service") 77 { 78 type = UnitType::service; 79 } 80 else if (typeStr == "socket") 81 { 82 type = UnitType::socket; 83 } 84 // get instance name if available 85 auto instancePos = fullUnitName.rfind("@"); 86 if (instancePos != std::string::npos) 87 { 88 instanceName = 89 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1); 90 unitName = fullUnitName.substr(0, instancePos); 91 } 92 else 93 { 94 unitName = fullUnitName.substr(0, typePos); 95 } 96 } 97 return std::make_tuple(unitName, type, instanceName); 98 } 99 100 static inline void 101 handleListUnitsResponse(sdbusplus::asio::object_server& server, 102 std::shared_ptr<sdbusplus::asio::connection>& conn, 103 boost::system::error_code /*ec*/, 104 const std::vector<ListUnitsType>& listUnits) 105 { 106 // Loop through all units, and mark all units, which has to be 107 // managed, irrespective of instance name. 108 for (const auto& unit : listUnits) 109 { 110 const auto& fullUnitName = 111 std::get<static_cast<int>(ListUnitElements::name)>(unit); 112 auto [unitName, type, instanceName] = 113 getUnitNameTypeAndInstance(fullUnitName); 114 if (std::find(serviceNames.begin(), serviceNames.end(), unitName) != 115 serviceNames.end()) 116 { 117 std::string instantiatedUnitName = 118 unitName + addInstanceName(instanceName, "_40"); 119 boost::replace_all(instantiatedUnitName, "-", "_2d"); 120 const sdbusplus::message::object_path& objectPath = 121 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit); 122 // Group the service & socket units togther.. Same services 123 // are managed together. 124 auto it = unitsToMonitor.find(instantiatedUnitName); 125 if (it != unitsToMonitor.end()) 126 { 127 auto& value = it->second; 128 if (type == UnitType::service) 129 { 130 std::get<static_cast<int>(monitorElement::unitName)>( 131 value) = unitName; 132 std::get<static_cast<int>(monitorElement::instanceName)>( 133 value) = instanceName; 134 std::get<static_cast<int>(monitorElement::serviceObjPath)>( 135 value) = objectPath; 136 } 137 else if (type == UnitType::socket) 138 { 139 std::get<static_cast<int>(monitorElement::socketObjPath)>( 140 value) = objectPath; 141 } 142 } 143 if (type == UnitType::service) 144 { 145 unitsToMonitor.emplace(instantiatedUnitName, 146 std::make_tuple(unitName, instanceName, 147 objectPath.str, "")); 148 } 149 else if (type == UnitType::socket) 150 { 151 unitsToMonitor.emplace( 152 instantiatedUnitName, 153 std::make_tuple("", "", "", objectPath.str)); 154 } 155 } 156 } 157 158 bool updateRequired = false; 159 bool jsonExist = std::filesystem::exists(srvCfgMgrFile); 160 if (jsonExist) 161 { 162 try 163 { 164 std::ifstream file(srvCfgMgrFile); 165 cereal::JSONInputArchive archive(file); 166 MonitorListMap savedMonitorList; 167 archive(savedMonitorList); 168 169 // compare the unit list read from systemd1 and the save list. 170 MonitorListMap diffMap; 171 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor), 172 begin(savedMonitorList), end(savedMonitorList), 173 std::inserter(diffMap, begin(diffMap))); 174 for (auto& unitIt : diffMap) 175 { 176 auto it = savedMonitorList.find(unitIt.first); 177 if (it == savedMonitorList.end()) 178 { 179 savedMonitorList.insert(unitIt); 180 updateRequired = true; 181 } 182 } 183 unitsToMonitor = savedMonitorList; 184 } 185 catch (const std::exception& e) 186 { 187 lg2::error( 188 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.", 189 "FILEPATH", srvCfgMgrFile, "ERROR", e); 190 191 // The "bad" files need to be moved to /tmp/ so that we can try to 192 // find out the cause of the file corruption. If we encounter this 193 // failure multiple times, we will only overwrite it to ensure that 194 // we don't accidentally fill up /tmp/. 195 std::error_code ec; 196 std::filesystem::copy_file( 197 srvCfgMgrFile, tmpFileBad, 198 std::filesystem::copy_options::overwrite_existing, ec); 199 if (ec) 200 { 201 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.", 202 "SRCFILE", srvCfgMgrFile, "DSTFILE", tmpFileBad); 203 } 204 205 updateRequired = true; 206 } 207 } 208 if (!jsonExist || updateRequired) 209 { 210 std::ofstream file(srvCfgMgrFile); 211 cereal::JSONOutputArchive archive(file); 212 archive(CEREAL_NVP(unitsToMonitor)); 213 } 214 215 #ifdef USB_CODE_UPDATE 216 unitsToMonitor.emplace( 217 "phosphor_2dusb_2dcode_2dupdate", 218 std::make_tuple( 219 "phosphor_usb_code_update", "", 220 "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice", 221 "")); 222 #endif 223 224 // create objects for needed services 225 for (auto& it : unitsToMonitor) 226 { 227 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) + 228 "/" + it.first); 229 std::string instanciatedUnitName = 230 std::get<static_cast<int>(monitorElement::unitName)>(it.second) + 231 addInstanceName( 232 std::get<static_cast<int>(monitorElement::instanceName)>( 233 it.second), 234 "@"); 235 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>( 236 server, conn, objPath, 237 std::get<static_cast<int>(monitorElement::unitName)>(it.second), 238 std::get<static_cast<int>(monitorElement::instanceName)>(it.second), 239 std::get<static_cast<int>(monitorElement::serviceObjPath)>( 240 it.second), 241 std::get<static_cast<int>(monitorElement::socketObjPath)>( 242 it.second)); 243 srvMgrObjects.emplace( 244 std::make_pair(std::move(objPath), std::move(srvCfgObj))); 245 } 246 } 247 248 void init(sdbusplus::asio::object_server& server, 249 std::shared_ptr<sdbusplus::asio::connection>& conn) 250 { 251 // Go through all systemd units, and dynamically detect and manage 252 // the service daemons 253 conn->async_method_call( 254 [&server, &conn](boost::system::error_code ec, 255 const std::vector<ListUnitsType>& listUnits) { 256 if (ec) 257 { 258 lg2::error("async_method_call error: ListUnits failed: {EC}", 259 "EC", ec.value()); 260 return; 261 } 262 handleListUnitsResponse(server, conn, ec, listUnits); 263 }, 264 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits"); 265 } 266 267 void checkAndInit(sdbusplus::asio::object_server& server, 268 std::shared_ptr<sdbusplus::asio::connection>& conn) 269 { 270 // Check whether systemd completed all the loading before initializing 271 conn->async_method_call( 272 [&server, &conn](boost::system::error_code ec, 273 const std::variant<uint64_t>& value) { 274 if (ec) 275 { 276 lg2::error("async_method_call error: ListUnits failed: {EC}", 277 "EC", ec.value()); 278 return; 279 } 280 if (std::get<uint64_t>(value)) 281 { 282 if (!unitQueryStarted) 283 { 284 unitQueryStarted = true; 285 init(server, conn); 286 } 287 } 288 else 289 { 290 // FIX-ME: Latest up-stream sync caused issue in receiving 291 // StartupFinished signal. Unable to get StartupFinished signal 292 // from systemd1 hence using poll method too, to trigger it 293 // properly. 294 constexpr size_t pollTimeout = 10; // seconds 295 initTimer->expires_after(std::chrono::seconds(pollTimeout)); 296 initTimer->async_wait([&server, &conn]( 297 const boost::system::error_code& ec) { 298 if (ec == boost::asio::error::operation_aborted) 299 { 300 // Timer reset. 301 return; 302 } 303 if (ec) 304 { 305 lg2::error( 306 "service config mgr - init - async wait error: {EC}", 307 "EC", ec.value()); 308 return; 309 } 310 checkAndInit(server, conn); 311 }); 312 } 313 }, 314 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf, 315 "FinishTimestamp"); 316 } 317 318 int main() 319 { 320 boost::asio::io_service io; 321 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 322 timer = std::make_unique<boost::asio::steady_timer>(io); 323 initTimer = std::make_unique<boost::asio::steady_timer>(io); 324 conn->request_name(phosphor::service::serviceConfigSrvName); 325 auto server = sdbusplus::asio::object_server(conn, true); 326 server.add_manager(phosphor::service::srcCfgMgrBasePath); 327 // Initialize the objects after systemd indicated startup finished. 328 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>( 329 static_cast<sdbusplus::bus::bus&>(*conn), 330 "type='signal'," 331 "member='StartupFinished',path='/org/freedesktop/systemd1'," 332 "interface='org.freedesktop.systemd1.Manager'", 333 [&server, &conn](sdbusplus::message::message& /*msg*/) { 334 if (!unitQueryStarted) 335 { 336 unitQueryStarted = true; 337 init(server, conn); 338 } 339 }); 340 // this will make sure to initialize the objects, when daemon is 341 // restarted. 342 checkAndInit(server, conn); 343 344 io.run(); 345 346 return 0; 347 } 348