blob: 038d11a4eb9720a6facd05fceefda4774465c735 [file] [log] [blame]
Vernon Maueryba2c0832020-07-15 10:02:38 -07001/*
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/asio/spawn.hpp>
19
20#include <fstream>
21#include <regex>
22
23extern std::unique_ptr<boost::asio::steady_timer> timer;
24extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
25 srvMgrObjects;
26static bool updateInProgress = false;
27
28namespace phosphor
29{
30namespace service
31{
32
33static constexpr const char* overrideConfFileName = "override.conf";
34static constexpr const size_t restartTimeout = 15; // seconds
35
36static constexpr const char* systemd1UnitBasePath =
37 "/org/freedesktop/systemd1/unit/";
38static constexpr const char* systemdOverrideUnitBasePath =
39 "/etc/systemd/system/";
40
41void ServiceConfig::updateSocketProperties(
42 const boost::container::flat_map<std::string, VariantType>& propertyMap)
43{
44 auto listenIt = propertyMap.find("Listen");
45 if (listenIt != propertyMap.end())
46 {
47 auto listenVal =
48 std::get<std::vector<std::tuple<std::string, std::string>>>(
49 listenIt->second);
50 if (listenVal.size())
51 {
52 protocol = std::get<0>(listenVal[0]);
53 std::string port = std::get<1>(listenVal[0]);
54 auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1),
55 nullptr, 10);
56 if (tmp > std::numeric_limits<uint16_t>::max())
57 {
58 throw std::out_of_range("Out of range");
59 }
60 portNum = tmp;
61 if (sockAttrIface && sockAttrIface->is_initialized())
62 {
63 internalSet = true;
64 sockAttrIface->set_property(sockAttrPropPort, portNum);
65 internalSet = false;
66 }
67 }
68 }
69}
70
71void ServiceConfig::updateServiceProperties(
72 const boost::container::flat_map<std::string, VariantType>& propertyMap)
73{
74 auto stateIt = propertyMap.find("UnitFileState");
75 if (stateIt != propertyMap.end())
76 {
77 stateValue = std::get<std::string>(stateIt->second);
78 unitEnabledState = unitMaskedState = false;
79 if (stateValue == stateMasked)
80 {
81 unitMaskedState = true;
82 }
83 else if (stateValue == stateEnabled)
84 {
85 unitEnabledState = true;
86 }
87 if (srvCfgIface && srvCfgIface->is_initialized())
88 {
89 internalSet = true;
90 srvCfgIface->set_property(srvCfgPropMasked, unitMaskedState);
91 srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
92 internalSet = false;
93 }
94 }
95 auto subStateIt = propertyMap.find("SubState");
96 if (subStateIt != propertyMap.end())
97 {
98 subStateValue = std::get<std::string>(subStateIt->second);
99 if (subStateValue == subStateRunning)
100 {
101 unitRunningState = true;
102 }
103 if (srvCfgIface && srvCfgIface->is_initialized())
104 {
105 internalSet = true;
106 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
107 internalSet = false;
108 }
109 }
110}
111
112void ServiceConfig::queryAndUpdateProperties()
113{
114 conn->async_method_call(
115 [this](boost::system::error_code ec,
116 const boost::container::flat_map<std::string, VariantType>&
117 propertyMap) {
118 if (ec)
119 {
120 phosphor::logging::log<phosphor::logging::level::ERR>(
121 "async_method_call error: Failed to service unit "
122 "properties");
123 return;
124 }
125 try
126 {
127 updateServiceProperties(propertyMap);
128 if (!socketObjectPath.empty())
129 {
130 conn->async_method_call(
131 [this](boost::system::error_code ec,
132 const boost::container::flat_map<
133 std::string, VariantType>& propertyMap) {
134 if (ec)
135 {
136 phosphor::logging::log<
137 phosphor::logging::level::ERR>(
138 "async_method_call error: Failed to get "
139 "all property");
140 return;
141 }
142 try
143 {
144 updateSocketProperties(propertyMap);
145 if (!srvCfgIface)
146 {
147 registerProperties();
148 }
149 }
150 catch (const std::exception& e)
151 {
152 phosphor::logging::log<
153 phosphor::logging::level::ERR>(
154 "Exception in getting socket properties",
155 phosphor::logging::entry("WHAT=%s",
156 e.what()));
157 return;
158 }
159 },
160 sysdService, socketObjectPath, dBusPropIntf,
161 dBusGetAllMethod, sysdSocketIntf);
162 }
163 else if (!srvCfgIface)
164 {
165 registerProperties();
166 }
167 }
168 catch (const std::exception& e)
169 {
170 phosphor::logging::log<phosphor::logging::level::ERR>(
171 "Exception in getting socket properties",
172 phosphor::logging::entry("WHAT=%s", e.what()));
173 return;
174 }
175 },
176 sysdService, serviceObjectPath, dBusPropIntf, dBusGetAllMethod,
177 sysdUnitIntf);
178 return;
179}
180
181void ServiceConfig::createSocketOverrideConf()
182{
183 if (!socketObjectPath.empty())
184 {
185 std::string socketUnitName(instantiatedUnitName + ".socket");
186 /// Check override socket directory exist, if not create it.
187 std::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath);
188 ovrUnitFileDir += socketUnitName;
189 ovrUnitFileDir += ".d";
190 if (!std::filesystem::exists(ovrUnitFileDir))
191 {
192 if (!std::filesystem::create_directories(ovrUnitFileDir))
193 {
194 phosphor::logging::log<phosphor::logging::level::ERR>(
195 "Unable to create the directory.",
196 phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
197 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
198 Common::Error::InternalFailure>();
199 }
200 }
201 overrideConfDir = std::string(ovrUnitFileDir);
202 }
203}
204
205ServiceConfig::ServiceConfig(
206 sdbusplus::asio::object_server& srv_,
207 std::shared_ptr<sdbusplus::asio::connection>& conn_,
208 const std::string& objPath_, const std::string& baseUnitName_,
209 const std::string& instanceName_, const std::string& serviceObjPath_,
210 const std::string& socketObjPath_) :
211 conn(conn_),
212 server(srv_), objPath(objPath_), baseUnitName(baseUnitName_),
213 instanceName(instanceName_), serviceObjectPath(serviceObjPath_),
214 socketObjectPath(socketObjPath_)
215{
216 instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@");
217 updatedFlag = 0;
218 queryAndUpdateProperties();
219 return;
220}
221
222std::string ServiceConfig::getSocketUnitName()
223{
224 return instantiatedUnitName + ".socket";
225}
226
227std::string ServiceConfig::getServiceUnitName()
228{
229 return instantiatedUnitName + ".service";
230}
231
232bool ServiceConfig::isMaskedOut()
233{
234 // return true if state is masked & no request to update the maskedState
235 return (
236 stateValue == "masked" &&
237 !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState))));
238}
239
240void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield)
241{
242 if (!updatedFlag || isMaskedOut())
243 {
244 // No updates / masked - Just return.
245 return;
246 }
247 phosphor::logging::log<phosphor::logging::level::INFO>(
248 "Applying new settings.",
249 phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
250 if (subStateValue == "running")
251 {
252 if (!socketObjectPath.empty())
253 {
254 systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
255 }
256 systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
257 }
258
259 if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port)))
260 {
261 createSocketOverrideConf();
262 // Create override config file and write data.
263 std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName};
264 std::string tmpFile{ovrCfgFile + "_tmp"};
265 std::ofstream cfgFile(tmpFile, std::ios::out);
266 if (!cfgFile.good())
267 {
268 phosphor::logging::log<phosphor::logging::level::ERR>(
269 "Failed to open override.conf_tmp file");
270 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
271 Error::InternalFailure>();
272 }
273
274 // Write the socket header
275 cfgFile << "[Socket]\n";
276 // Listen
277 cfgFile << "Listen" << protocol << "="
278 << "\n";
279 cfgFile << "Listen" << protocol << "=" << portNum << "\n";
280 cfgFile.close();
281
282 if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
283 {
284 phosphor::logging::log<phosphor::logging::level::ERR>(
285 "Failed to rename tmp file as override.conf");
286 std::remove(tmpFile.c_str());
287 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
288 Error::InternalFailure>();
289 }
290 }
291
292 if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
293 (1 << static_cast<uint8_t>(UpdatedProp::enabledState))))
294 {
295 std::vector<std::string> unitFiles;
296 if (socketObjectPath.empty())
297 {
298 unitFiles = {getServiceUnitName()};
299 }
300 else
301 {
302 unitFiles = {getSocketUnitName(), getServiceUnitName()};
303 }
304 systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue,
305 unitMaskedState, unitEnabledState);
306 }
307 return;
308}
309void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield)
310{
311 if (!updatedFlag || isMaskedOut())
312 {
313 // No updates. Just return.
314 return;
315 }
316
317 if (unitRunningState)
318 {
319 if (!socketObjectPath.empty())
320 {
321 systemdUnitAction(conn, yield, getSocketUnitName(),
322 sysdRestartUnit);
323 }
324 systemdUnitAction(conn, yield, getServiceUnitName(), sysdRestartUnit);
325 }
326
327 // Reset the flag
328 updatedFlag = 0;
329
330 phosphor::logging::log<phosphor::logging::level::INFO>(
331 "Applied new settings",
332 phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
333
334 queryAndUpdateProperties();
335 return;
336}
337
338void ServiceConfig::startServiceRestartTimer()
339{
340 timer->expires_after(std::chrono::seconds(restartTimeout));
341 timer->async_wait([this](const boost::system::error_code& ec) {
342 if (ec == boost::asio::error::operation_aborted)
343 {
344 // Timer reset.
345 return;
346 }
347 else if (ec)
348 {
349 phosphor::logging::log<phosphor::logging::level::ERR>(
350 "async wait error.");
351 return;
352 }
353 updateInProgress = true;
354 boost::asio::spawn(conn->get_io_context(),
355 [this](boost::asio::yield_context yield) {
356 // Stop and apply configuration for all objects
357 for (auto& srvMgrObj : srvMgrObjects)
358 {
359 auto& srvObj = srvMgrObj.second;
360 if (srvObj->updatedFlag)
361 {
362 srvObj->stopAndApplyUnitConfig(yield);
363 }
364 }
365 // Do system reload
366 systemdDaemonReload(conn, yield);
367 // restart unit config.
368 for (auto& srvMgrObj : srvMgrObjects)
369 {
370 auto& srvObj = srvMgrObj.second;
371 if (srvObj->updatedFlag)
372 {
373 srvObj->restartUnitConfig(yield);
374 }
375 }
376 updateInProgress = false;
377 });
378 });
379}
380
381void ServiceConfig::registerProperties()
382{
383 srvCfgIface = server.add_interface(objPath, serviceConfigIntfName);
384
385 if (!socketObjectPath.empty())
386 {
387 sockAttrIface = server.add_interface(objPath, sockAttrIntfName);
388 sockAttrIface->register_property(
389 sockAttrPropPort, portNum,
390 [this](const uint16_t& req, uint16_t& res) {
391 if (!internalSet)
392 {
393 if (req == res)
394 {
395 return 1;
396 }
397 if (updateInProgress)
398 {
399 return 0;
400 }
401 portNum = req;
402 updatedFlag |=
403 (1 << static_cast<uint8_t>(UpdatedProp::port));
404 startServiceRestartTimer();
405 }
406 res = req;
407 return 1;
408 });
409 }
410
411 srvCfgIface->register_property(
412 srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) {
413 if (!internalSet)
414 {
415 if (req == res)
416 {
417 return 1;
418 }
419 if (updateInProgress)
420 {
421 return 0;
422 }
423 unitMaskedState = req;
424 unitEnabledState = !unitMaskedState;
425 unitRunningState = !unitMaskedState;
426 updatedFlag |=
427 (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
428 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
429 (1 << static_cast<uint8_t>(UpdatedProp::runningState));
430 internalSet = true;
431 srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
432 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
433 internalSet = false;
434 startServiceRestartTimer();
435 }
436 res = req;
437 return 1;
438 });
439
440 srvCfgIface->register_property(
441 srvCfgPropEnabled, unitEnabledState,
442 [this](const bool& req, bool& res) {
443 if (!internalSet)
444 {
445 if (req == res)
446 {
447 return 1;
448 }
449 if (updateInProgress)
450 {
451 return 0;
452 }
453 if (unitMaskedState)
454 { // block updating if masked
455 phosphor::logging::log<phosphor::logging::level::ERR>(
456 "Invalid value specified");
457 return -EINVAL;
458 }
459 unitEnabledState = req;
460 updatedFlag |=
461 (1 << static_cast<uint8_t>(UpdatedProp::enabledState));
462 startServiceRestartTimer();
463 }
464 res = req;
465 return 1;
466 });
467
468 srvCfgIface->register_property(
469 srvCfgPropRunning, unitRunningState,
470 [this](const bool& req, bool& res) {
471 if (!internalSet)
472 {
473 if (req == res)
474 {
475 return 1;
476 }
477 if (updateInProgress)
478 {
479 return 0;
480 }
481 if (unitMaskedState)
482 { // block updating if masked
483 phosphor::logging::log<phosphor::logging::level::ERR>(
484 "Invalid value specified");
485 return -EINVAL;
486 }
487 unitRunningState = req;
488 updatedFlag |=
489 (1 << static_cast<uint8_t>(UpdatedProp::runningState));
490 startServiceRestartTimer();
491 }
492 res = req;
493 return 1;
494 });
495
496 srvCfgIface->initialize();
497 if (!socketObjectPath.empty())
498 {
499 sockAttrIface->initialize();
500 }
501 return;
502}
503
504} // namespace service
505} // namespace phosphor