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