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 <fstream> 17 #include <regex> 18 #include "srvcfg_manager.hpp" 19 20 extern std::shared_ptr<boost::asio::deadline_timer> timer; 21 extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>> 22 srvMgrObjects; 23 24 namespace phosphor 25 { 26 namespace service 27 { 28 29 static constexpr const char *overrideConfFileName = "override.conf"; 30 static constexpr const size_t restartTimeout = 15; // seconds 31 32 static constexpr const char *systemd1UnitBasePath = 33 "/org/freedesktop/systemd1/unit/"; 34 static constexpr const char *systemdOverrideUnitBasePath = 35 "/etc/systemd/system/"; 36 37 void ServiceConfig::syncWithSystemD1Properties() 38 { 39 // Read systemd1 socket/service property and load. 40 conn->async_method_call( 41 [this](boost::system::error_code ec, 42 const sdbusplus::message::variant< 43 std::vector<std::tuple<std::string, std::string>>> &value) { 44 if (ec) 45 { 46 phosphor::logging::log<phosphor::logging::level::ERR>( 47 "async_method_call error: Failed to get property"); 48 return; 49 } 50 51 try 52 { 53 auto listenVal = sdbusplus::message::variant_ns::get< 54 std::vector<std::tuple<std::string, std::string>>>(value); 55 protocol = std::get<0>(listenVal[0]); 56 std::string port = std::get<1>(listenVal[0]); 57 auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1), 58 nullptr, 10); 59 if (tmp > std::numeric_limits<uint16_t>::max()) 60 { 61 throw std::out_of_range("Out of range"); 62 } 63 portNum = tmp; 64 } 65 catch (const std::exception &e) 66 { 67 phosphor::logging::log<phosphor::logging::level::ERR>( 68 "Exception for port number", 69 phosphor::logging::entry("WHAT=%s", e.what())); 70 return; 71 } 72 conn->async_method_call( 73 [](boost::system::error_code ec) { 74 if (ec) 75 { 76 phosphor::logging::log<phosphor::logging::level::ERR>( 77 "async_method_call error: Failed to set property"); 78 return; 79 } 80 }, 81 serviceConfigSrvName, objPath.c_str(), 82 "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName, 83 "Port", sdbusplus::message::variant<uint16_t>(portNum)); 84 }, 85 "org.freedesktop.systemd1", sysDSockObjPath.c_str(), 86 "org.freedesktop.DBus.Properties", "Get", 87 "org.freedesktop.systemd1.Socket", "Listen"); 88 89 conn->async_method_call( 90 [this](boost::system::error_code ec, 91 const sdbusplus::message::variant<std::string> &pValue) { 92 if (ec) 93 { 94 phosphor::logging::log<phosphor::logging::level::ERR>( 95 "async_method_call error: Failed to get property"); 96 return; 97 } 98 99 channelList.clear(); 100 std::istringstream stm( 101 sdbusplus::message::variant_ns::get<std::string>(pValue)); 102 std::string token; 103 while (std::getline(stm, token, ',')) 104 { 105 channelList.push_back(token); 106 } 107 conn->async_method_call( 108 [](boost::system::error_code ec) { 109 if (ec) 110 { 111 phosphor::logging::log<phosphor::logging::level::ERR>( 112 "async_method_call error: Failed to set property"); 113 return; 114 } 115 }, 116 serviceConfigSrvName, objPath.c_str(), 117 "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName, 118 "Channel", 119 sdbusplus::message::variant<std::vector<std::string>>( 120 channelList)); 121 }, 122 "org.freedesktop.systemd1", sysDSockObjPath.c_str(), 123 "org.freedesktop.DBus.Properties", "Get", 124 "org.freedesktop.systemd1.Socket", "BindToDevice"); 125 126 std::string srvUnitName(sysDUnitName); 127 if (srvUnitName == "dropbear") 128 { 129 srvUnitName.append("@"); 130 } 131 srvUnitName.append(".service"); 132 conn->async_method_call( 133 [this](boost::system::error_code ec, const std::string &pValue) { 134 if (ec) 135 { 136 phosphor::logging::log<phosphor::logging::level::ERR>( 137 "async_method_call error: Failed to get property"); 138 return; 139 } 140 stateValue = pValue; 141 conn->async_method_call( 142 [](boost::system::error_code ec) { 143 if (ec) 144 { 145 phosphor::logging::log<phosphor::logging::level::ERR>( 146 "async_method_call error: Failed to set property"); 147 return; 148 } 149 }, 150 serviceConfigSrvName, objPath.c_str(), 151 "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName, 152 "State", sdbusplus::message::variant<std::string>(stateValue)); 153 }, 154 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 155 "org.freedesktop.systemd1.Manager", "GetUnitFileState", srvUnitName); 156 157 return; 158 } 159 160 ServiceConfig::ServiceConfig( 161 sdbusplus::asio::object_server &srv_, 162 std::shared_ptr<sdbusplus::asio::connection> &conn_, std::string objPath_, 163 std::string unitName) : 164 server(srv_), 165 conn(conn_), objPath(objPath_), sysDUnitName(unitName) 166 { 167 std::string socketUnitName(sysDUnitName + ".socket"); 168 // .socket systemd service files are handled. 169 // Regular .service only files are ignored. 170 if (!checkSystemdUnitExist(socketUnitName)) 171 { 172 phosphor::logging::log<phosphor::logging::level::ERR>( 173 "Unit doesn't exist.", 174 phosphor::logging::entry("UNITNAME=%s", socketUnitName.c_str())); 175 phosphor::logging::elog< 176 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>(); 177 } 178 179 /// Check override socket directory exist, if not create it. 180 std::experimental::filesystem::path ovrUnitFileDir( 181 systemdOverrideUnitBasePath); 182 ovrUnitFileDir += socketUnitName; 183 ovrUnitFileDir += ".d"; 184 if (!std::experimental::filesystem::exists(ovrUnitFileDir)) 185 { 186 if (!std::experimental::filesystem::create_directories(ovrUnitFileDir)) 187 { 188 phosphor::logging::log<phosphor::logging::level::ERR>( 189 "Unable to create the directory.", 190 phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str())); 191 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common:: 192 Error::InternalFailure>(); 193 } 194 } 195 196 /* Store require info locally */ 197 unitSocketFilePath = std::string(ovrUnitFileDir); 198 199 sysDSockObjPath = systemd1UnitBasePath; 200 sysDSockObjPath.append( 201 std::regex_replace(sysDUnitName, std::regex("-"), "_2d")); 202 sysDSockObjPath.append("_2esocket"); 203 204 // Adds interface, object and Properties.... 205 registerProperties(); 206 207 syncWithSystemD1Properties(); 208 209 updatedFlag = 0; 210 return; 211 } 212 213 void ServiceConfig::applySystemDServiceConfig() 214 { 215 phosphor::logging::log<phosphor::logging::level::INFO>( 216 "Applying new settings.", 217 phosphor::logging::entry("OBJPATH=%s", objPath.c_str())); 218 if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::channel)) | 219 (1 << static_cast<uint8_t>(UpdatedProp::port)))) 220 { 221 // Create override config file and write data. 222 std::string ovrCfgFile{unitSocketFilePath + "/" + overrideConfFileName}; 223 std::string tmpFile{ovrCfgFile + "_tmp"}; 224 std::ofstream cfgFile(tmpFile, std::ios::out); 225 if (!cfgFile.good()) 226 { 227 phosphor::logging::log<phosphor::logging::level::ERR>( 228 "Failed to open override.conf_tmp file"); 229 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common:: 230 Error::InternalFailure>(); 231 } 232 233 // Write the socket header 234 cfgFile << "[Socket]\n"; 235 // Listen 236 cfgFile << "Listen" << protocol << "=" 237 << "\n"; 238 cfgFile << "Listen" << protocol << "=" << portNum << "\n"; 239 // BindToDevice 240 bool firstElement = true; 241 cfgFile << "BindToDevice="; 242 for (const auto &it : channelList) 243 { 244 if (firstElement) 245 { 246 cfgFile << it; 247 firstElement = false; 248 } 249 else 250 { 251 cfgFile << "," << it; 252 } 253 } 254 cfgFile.close(); 255 256 if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0) 257 { 258 phosphor::logging::log<phosphor::logging::level::ERR>( 259 "Failed to rename tmp file as override.conf"); 260 std::remove(tmpFile.c_str()); 261 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common:: 262 Error::InternalFailure>(); 263 } 264 265 // Systemd forcing explicit socket stop before reload...! 266 std::string socketUnitName(sysDUnitName + ".socket"); 267 systemdUnitAction(conn, socketUnitName, sysdActionStopUnit); 268 269 std::string srvUnitName(sysDUnitName + ".service"); 270 systemdUnitAction(conn, srvUnitName, sysdActionStopUnit); 271 272 // Perform daemon reload to read new settings 273 systemdDaemonReload(conn); 274 275 // Restart the unit 276 systemdUnitAction(conn, socketUnitName, sysdActionStartUnit); 277 systemdUnitAction(conn, srvUnitName, sysdActionStartUnit); 278 } 279 280 if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state))) 281 { 282 if ((stateValue == "enabled") || (stateValue == "disabled")) 283 { 284 systemdUnitFileStateChange(conn, sysDUnitName, stateValue); 285 } 286 } 287 288 // Reset the flag 289 updatedFlag = 0; 290 291 // All done. Lets reload the properties which are applied on systemd1. 292 // TODO: We need to capture the service restart signal and reload data 293 // inside the signal handler. So that we can update the service properties 294 // modified, outside of this service as well. 295 syncWithSystemD1Properties(); 296 297 return; 298 } 299 300 void ServiceConfig::startServiceRestartTimer() 301 { 302 timer->expires_from_now(boost::posix_time::seconds(restartTimeout)); 303 timer->async_wait([this](const boost::system::error_code &ec) { 304 if (ec == boost::asio::error::operation_aborted) 305 { 306 // Timer reset. 307 return; 308 } 309 else if (ec) 310 { 311 phosphor::logging::log<phosphor::logging::level::ERR>( 312 "async wait error."); 313 return; 314 } 315 for (auto &srvMgrObj : srvMgrObjects) 316 { 317 auto &srvObj = srvMgrObj.second; 318 if (srvObj->updatedFlag) 319 { 320 srvObj->applySystemDServiceConfig(); 321 } 322 } 323 }); 324 } 325 326 void ServiceConfig::registerProperties() 327 { 328 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 329 server.add_interface(objPath, serviceConfigIntfName); 330 331 iface->register_property( 332 "Port", portNum, [this](const uint16_t &req, uint16_t &res) { 333 phosphor::logging::log<phosphor::logging::level::ERR>( 334 " Inside register_property"); 335 if (req == res) 336 { 337 return 1; 338 } 339 portNum = req; 340 updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::port)); 341 startServiceRestartTimer(); 342 res = req; 343 return 1; 344 }); 345 346 iface->register_property( 347 "Channel", channelList, 348 [this](const std::vector<std::string> &req, 349 std::vector<std::string> &res) { 350 if (req == res) 351 { 352 return 1; 353 } 354 channelList.clear(); 355 std::copy(req.begin(), req.end(), back_inserter(channelList)); 356 357 updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::channel)); 358 startServiceRestartTimer(); 359 res = req; 360 return 1; 361 }); 362 363 iface->register_property( 364 "State", stateValue, [this](const std::string &req, std::string &res) { 365 if (req == res) 366 { 367 return 1; 368 } 369 if ((req != "enabled") && (req != "disabled") && (req != "static")) 370 { 371 phosphor::logging::log<phosphor::logging::level::ERR>( 372 "Invalid value specified"); 373 return -EINVAL; 374 } 375 stateValue = req; 376 updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::state)); 377 startServiceRestartTimer(); 378 res = req; 379 return 1; 380 }); 381 382 iface->initialize(); 383 return; 384 } 385 386 } // namespace service 387 } // namespace phosphor 388