blob: bd23b44a775fcee74bca02b487e37deb5ea194aa [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);
George Liua19b5092021-05-24 15:54:02 +080099 if (subStateValue == subStateRunning ||
100 subStateValue == subStateListening)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700101 {
102 unitRunningState = true;
103 }
104 if (srvCfgIface && srvCfgIface->is_initialized())
105 {
106 internalSet = true;
107 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
108 internalSet = false;
109 }
110 }
111}
112
113void ServiceConfig::queryAndUpdateProperties()
114{
George Liua19b5092021-05-24 15:54:02 +0800115 std::string objectPath =
116 isDropBearService ? socketObjectPath : serviceObjectPath;
117 if (objectPath.empty())
118 {
119 return;
120 }
121
Vernon Maueryba2c0832020-07-15 10:02:38 -0700122 conn->async_method_call(
123 [this](boost::system::error_code ec,
124 const boost::container::flat_map<std::string, VariantType>&
125 propertyMap) {
126 if (ec)
127 {
128 phosphor::logging::log<phosphor::logging::level::ERR>(
129 "async_method_call error: Failed to service unit "
130 "properties");
131 return;
132 }
133 try
134 {
135 updateServiceProperties(propertyMap);
136 if (!socketObjectPath.empty())
137 {
138 conn->async_method_call(
139 [this](boost::system::error_code ec,
140 const boost::container::flat_map<
141 std::string, VariantType>& propertyMap) {
142 if (ec)
143 {
144 phosphor::logging::log<
145 phosphor::logging::level::ERR>(
146 "async_method_call error: Failed to get "
147 "all property");
148 return;
149 }
150 try
151 {
152 updateSocketProperties(propertyMap);
153 if (!srvCfgIface)
154 {
155 registerProperties();
156 }
157 }
158 catch (const std::exception& e)
159 {
160 phosphor::logging::log<
161 phosphor::logging::level::ERR>(
162 "Exception in getting socket properties",
163 phosphor::logging::entry("WHAT=%s",
164 e.what()));
165 return;
166 }
167 },
168 sysdService, socketObjectPath, dBusPropIntf,
169 dBusGetAllMethod, sysdSocketIntf);
170 }
171 else if (!srvCfgIface)
172 {
173 registerProperties();
174 }
175 }
176 catch (const std::exception& e)
177 {
178 phosphor::logging::log<phosphor::logging::level::ERR>(
179 "Exception in getting socket properties",
180 phosphor::logging::entry("WHAT=%s", e.what()));
181 return;
182 }
183 },
George Liua19b5092021-05-24 15:54:02 +0800184 sysdService, objectPath, dBusPropIntf, dBusGetAllMethod, sysdUnitIntf);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700185 return;
186}
187
188void ServiceConfig::createSocketOverrideConf()
189{
190 if (!socketObjectPath.empty())
191 {
192 std::string socketUnitName(instantiatedUnitName + ".socket");
193 /// Check override socket directory exist, if not create it.
194 std::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath);
195 ovrUnitFileDir += socketUnitName;
196 ovrUnitFileDir += ".d";
197 if (!std::filesystem::exists(ovrUnitFileDir))
198 {
199 if (!std::filesystem::create_directories(ovrUnitFileDir))
200 {
201 phosphor::logging::log<phosphor::logging::level::ERR>(
202 "Unable to create the directory.",
203 phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
204 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
205 Common::Error::InternalFailure>();
206 }
207 }
208 overrideConfDir = std::string(ovrUnitFileDir);
209 }
210}
211
212ServiceConfig::ServiceConfig(
213 sdbusplus::asio::object_server& srv_,
214 std::shared_ptr<sdbusplus::asio::connection>& conn_,
215 const std::string& objPath_, const std::string& baseUnitName_,
216 const std::string& instanceName_, const std::string& serviceObjPath_,
217 const std::string& socketObjPath_) :
218 conn(conn_),
219 server(srv_), objPath(objPath_), baseUnitName(baseUnitName_),
220 instanceName(instanceName_), serviceObjectPath(serviceObjPath_),
221 socketObjectPath(socketObjPath_)
222{
George Liua19b5092021-05-24 15:54:02 +0800223 if (baseUnitName == "dropbear")
224 {
225 isDropBearService = true;
226 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700227 instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@");
228 updatedFlag = 0;
229 queryAndUpdateProperties();
230 return;
231}
232
233std::string ServiceConfig::getSocketUnitName()
234{
235 return instantiatedUnitName + ".socket";
236}
237
238std::string ServiceConfig::getServiceUnitName()
239{
240 return instantiatedUnitName + ".service";
241}
242
243bool ServiceConfig::isMaskedOut()
244{
245 // return true if state is masked & no request to update the maskedState
246 return (
247 stateValue == "masked" &&
248 !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState))));
249}
250
251void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield)
252{
253 if (!updatedFlag || isMaskedOut())
254 {
255 // No updates / masked - Just return.
256 return;
257 }
258 phosphor::logging::log<phosphor::logging::level::INFO>(
259 "Applying new settings.",
260 phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
George Liua19b5092021-05-24 15:54:02 +0800261 if (subStateValue == subStateRunning || subStateValue == subStateListening)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700262 {
263 if (!socketObjectPath.empty())
264 {
265 systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
266 }
George Liua19b5092021-05-24 15:54:02 +0800267 if (!isDropBearService)
268 {
269 systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
270 }
271 else
272 {
273 // Get the ListUnits property, find all the services of
274 // `dropbear@<ip><port>.service` and stop the service through
275 // the systemdUnitAction method
276 boost::system::error_code ec;
277 auto listUnits =
278 conn->yield_method_call<std::vector<ListUnitsType>>(
279 yield, ec, sysdService, sysdObjPath, sysdMgrIntf,
280 "ListUnits");
281
282 checkAndThrowInternalFailure(
283 ec, "yield_method_call error: ListUnits failed");
284
285 for (const auto& unit : listUnits)
286 {
287 const auto& service =
288 std::get<static_cast<int>(ListUnitElements::name)>(unit);
289 const auto& status =
290 std::get<static_cast<int>(ListUnitElements::subState)>(
291 unit);
292 if (service.find("dropbear@") != std::string::npos &&
293 service.find(".service") != std::string::npos &&
294 status == subStateRunning)
295 {
296 systemdUnitAction(conn, yield, service, sysdStopUnit);
297 }
298 }
299 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700300 }
301
302 if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port)))
303 {
304 createSocketOverrideConf();
305 // Create override config file and write data.
306 std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName};
307 std::string tmpFile{ovrCfgFile + "_tmp"};
308 std::ofstream cfgFile(tmpFile, std::ios::out);
309 if (!cfgFile.good())
310 {
311 phosphor::logging::log<phosphor::logging::level::ERR>(
312 "Failed to open override.conf_tmp file");
313 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
314 Error::InternalFailure>();
315 }
316
317 // Write the socket header
318 cfgFile << "[Socket]\n";
319 // Listen
320 cfgFile << "Listen" << protocol << "="
321 << "\n";
322 cfgFile << "Listen" << protocol << "=" << portNum << "\n";
323 cfgFile.close();
324
325 if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
326 {
327 phosphor::logging::log<phosphor::logging::level::ERR>(
328 "Failed to rename tmp file as override.conf");
329 std::remove(tmpFile.c_str());
330 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
331 Error::InternalFailure>();
332 }
333 }
334
335 if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
336 (1 << static_cast<uint8_t>(UpdatedProp::enabledState))))
337 {
338 std::vector<std::string> unitFiles;
339 if (socketObjectPath.empty())
340 {
341 unitFiles = {getServiceUnitName()};
342 }
George Liua19b5092021-05-24 15:54:02 +0800343 else if (!socketObjectPath.empty() && isDropBearService)
344 {
345 unitFiles = {getSocketUnitName()};
346 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700347 else
348 {
349 unitFiles = {getSocketUnitName(), getServiceUnitName()};
350 }
351 systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue,
352 unitMaskedState, unitEnabledState);
353 }
354 return;
355}
356void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield)
357{
358 if (!updatedFlag || isMaskedOut())
359 {
360 // No updates. Just return.
361 return;
362 }
363
364 if (unitRunningState)
365 {
366 if (!socketObjectPath.empty())
367 {
368 systemdUnitAction(conn, yield, getSocketUnitName(),
369 sysdRestartUnit);
370 }
George Liua19b5092021-05-24 15:54:02 +0800371 if (!isDropBearService)
372 {
373 systemdUnitAction(conn, yield, getServiceUnitName(),
374 sysdRestartUnit);
375 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700376 }
377
378 // Reset the flag
379 updatedFlag = 0;
380
381 phosphor::logging::log<phosphor::logging::level::INFO>(
382 "Applied new settings",
383 phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
384
385 queryAndUpdateProperties();
386 return;
387}
388
389void ServiceConfig::startServiceRestartTimer()
390{
391 timer->expires_after(std::chrono::seconds(restartTimeout));
392 timer->async_wait([this](const boost::system::error_code& ec) {
393 if (ec == boost::asio::error::operation_aborted)
394 {
395 // Timer reset.
396 return;
397 }
398 else if (ec)
399 {
400 phosphor::logging::log<phosphor::logging::level::ERR>(
401 "async wait error.");
402 return;
403 }
404 updateInProgress = true;
405 boost::asio::spawn(conn->get_io_context(),
406 [this](boost::asio::yield_context yield) {
407 // Stop and apply configuration for all objects
408 for (auto& srvMgrObj : srvMgrObjects)
409 {
410 auto& srvObj = srvMgrObj.second;
411 if (srvObj->updatedFlag)
412 {
413 srvObj->stopAndApplyUnitConfig(yield);
414 }
415 }
416 // Do system reload
417 systemdDaemonReload(conn, yield);
418 // restart unit config.
419 for (auto& srvMgrObj : srvMgrObjects)
420 {
421 auto& srvObj = srvMgrObj.second;
422 if (srvObj->updatedFlag)
423 {
424 srvObj->restartUnitConfig(yield);
425 }
426 }
427 updateInProgress = false;
428 });
429 });
430}
431
432void ServiceConfig::registerProperties()
433{
434 srvCfgIface = server.add_interface(objPath, serviceConfigIntfName);
435
436 if (!socketObjectPath.empty())
437 {
438 sockAttrIface = server.add_interface(objPath, sockAttrIntfName);
439 sockAttrIface->register_property(
440 sockAttrPropPort, portNum,
441 [this](const uint16_t& req, uint16_t& res) {
442 if (!internalSet)
443 {
444 if (req == res)
445 {
446 return 1;
447 }
448 if (updateInProgress)
449 {
450 return 0;
451 }
452 portNum = req;
453 updatedFlag |=
454 (1 << static_cast<uint8_t>(UpdatedProp::port));
455 startServiceRestartTimer();
456 }
457 res = req;
458 return 1;
459 });
460 }
461
462 srvCfgIface->register_property(
463 srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) {
464 if (!internalSet)
465 {
466 if (req == res)
467 {
468 return 1;
469 }
470 if (updateInProgress)
471 {
472 return 0;
473 }
474 unitMaskedState = req;
475 unitEnabledState = !unitMaskedState;
476 unitRunningState = !unitMaskedState;
477 updatedFlag |=
478 (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
479 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
480 (1 << static_cast<uint8_t>(UpdatedProp::runningState));
481 internalSet = true;
482 srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
483 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
484 internalSet = false;
485 startServiceRestartTimer();
486 }
487 res = req;
488 return 1;
489 });
490
491 srvCfgIface->register_property(
492 srvCfgPropEnabled, unitEnabledState,
493 [this](const bool& req, bool& res) {
494 if (!internalSet)
495 {
496 if (req == res)
497 {
498 return 1;
499 }
500 if (updateInProgress)
501 {
502 return 0;
503 }
504 if (unitMaskedState)
505 { // block updating if masked
506 phosphor::logging::log<phosphor::logging::level::ERR>(
507 "Invalid value specified");
508 return -EINVAL;
509 }
510 unitEnabledState = req;
511 updatedFlag |=
512 (1 << static_cast<uint8_t>(UpdatedProp::enabledState));
513 startServiceRestartTimer();
514 }
515 res = req;
516 return 1;
517 });
518
519 srvCfgIface->register_property(
520 srvCfgPropRunning, unitRunningState,
521 [this](const bool& req, bool& res) {
522 if (!internalSet)
523 {
524 if (req == res)
525 {
526 return 1;
527 }
528 if (updateInProgress)
529 {
530 return 0;
531 }
532 if (unitMaskedState)
533 { // block updating if masked
534 phosphor::logging::log<phosphor::logging::level::ERR>(
535 "Invalid value specified");
536 return -EINVAL;
537 }
538 unitRunningState = req;
539 updatedFlag |=
540 (1 << static_cast<uint8_t>(UpdatedProp::runningState));
541 startServiceRestartTimer();
542 }
543 res = req;
544 return 1;
545 });
546
547 srvCfgIface->initialize();
548 if (!socketObjectPath.empty())
549 {
550 sockAttrIface->initialize();
551 }
552 return;
553}
554
555} // namespace service
556} // namespace phosphor