blob: b7b891b789d701936df0a5ddeeebe811a8a899e0 [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 {
George Liucb267c82022-01-05 17:53:28 +0800128 lg2::error(
129 "async_method_call error: Failed to service unit properties: {EC}",
130 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700131 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 {
George Liucb267c82022-01-05 17:53:28 +0800144 lg2::error(
145 "async_method_call error: Failed to get all property: {EC}",
146 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700147 return;
148 }
149 try
150 {
151 updateSocketProperties(propertyMap);
152 if (!srvCfgIface)
153 {
154 registerProperties();
155 }
156 }
157 catch (const std::exception& e)
158 {
George Liucb267c82022-01-05 17:53:28 +0800159 lg2::error(
160 "Exception in getting socket properties: {ERROR}",
161 "ERROR", e);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700162 return;
163 }
164 },
165 sysdService, socketObjectPath, dBusPropIntf,
166 dBusGetAllMethod, sysdSocketIntf);
167 }
168 else if (!srvCfgIface)
169 {
170 registerProperties();
171 }
172 }
173 catch (const std::exception& e)
174 {
George Liucb267c82022-01-05 17:53:28 +0800175 lg2::error("Exception in getting socket properties: {ERROR}",
176 "ERROR", e);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700177 return;
178 }
179 },
George Liua19b5092021-05-24 15:54:02 +0800180 sysdService, objectPath, dBusPropIntf, dBusGetAllMethod, sysdUnitIntf);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700181 return;
182}
183
184void ServiceConfig::createSocketOverrideConf()
185{
186 if (!socketObjectPath.empty())
187 {
188 std::string socketUnitName(instantiatedUnitName + ".socket");
189 /// Check override socket directory exist, if not create it.
190 std::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath);
191 ovrUnitFileDir += socketUnitName;
192 ovrUnitFileDir += ".d";
193 if (!std::filesystem::exists(ovrUnitFileDir))
194 {
195 if (!std::filesystem::create_directories(ovrUnitFileDir))
196 {
George Liucb267c82022-01-05 17:53:28 +0800197 lg2::error("Unable to create the {DIR} directory.", "DIR",
198 ovrUnitFileDir);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700199 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
200 Common::Error::InternalFailure>();
201 }
202 }
203 overrideConfDir = std::string(ovrUnitFileDir);
204 }
205}
206
207ServiceConfig::ServiceConfig(
208 sdbusplus::asio::object_server& srv_,
209 std::shared_ptr<sdbusplus::asio::connection>& conn_,
210 const std::string& objPath_, const std::string& baseUnitName_,
211 const std::string& instanceName_, const std::string& serviceObjPath_,
212 const std::string& socketObjPath_) :
213 conn(conn_),
214 server(srv_), objPath(objPath_), baseUnitName(baseUnitName_),
215 instanceName(instanceName_), serviceObjectPath(serviceObjPath_),
216 socketObjectPath(socketObjPath_)
217{
George Liua19b5092021-05-24 15:54:02 +0800218 if (baseUnitName == "dropbear")
219 {
220 isDropBearService = true;
221 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700222 instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@");
223 updatedFlag = 0;
224 queryAndUpdateProperties();
225 return;
226}
227
228std::string ServiceConfig::getSocketUnitName()
229{
230 return instantiatedUnitName + ".socket";
231}
232
233std::string ServiceConfig::getServiceUnitName()
234{
235 return instantiatedUnitName + ".service";
236}
237
238bool ServiceConfig::isMaskedOut()
239{
240 // return true if state is masked & no request to update the maskedState
241 return (
242 stateValue == "masked" &&
243 !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState))));
244}
245
246void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield)
247{
248 if (!updatedFlag || isMaskedOut())
249 {
250 // No updates / masked - Just return.
251 return;
252 }
George Liucb267c82022-01-05 17:53:28 +0800253 lg2::info("Applying new settings: {OBJPATH}", "OBJPATH", objPath);
George Liua19b5092021-05-24 15:54:02 +0800254 if (subStateValue == subStateRunning || subStateValue == subStateListening)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700255 {
256 if (!socketObjectPath.empty())
257 {
258 systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
259 }
George Liua19b5092021-05-24 15:54:02 +0800260 if (!isDropBearService)
261 {
262 systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
263 }
264 else
265 {
266 // Get the ListUnits property, find all the services of
267 // `dropbear@<ip><port>.service` and stop the service through
268 // the systemdUnitAction method
269 boost::system::error_code ec;
270 auto listUnits =
271 conn->yield_method_call<std::vector<ListUnitsType>>(
272 yield, ec, sysdService, sysdObjPath, sysdMgrIntf,
273 "ListUnits");
274
275 checkAndThrowInternalFailure(
276 ec, "yield_method_call error: ListUnits failed");
277
278 for (const auto& unit : listUnits)
279 {
280 const auto& service =
281 std::get<static_cast<int>(ListUnitElements::name)>(unit);
282 const auto& status =
283 std::get<static_cast<int>(ListUnitElements::subState)>(
284 unit);
285 if (service.find("dropbear@") != std::string::npos &&
286 service.find(".service") != std::string::npos &&
287 status == subStateRunning)
288 {
289 systemdUnitAction(conn, yield, service, sysdStopUnit);
290 }
291 }
292 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700293 }
294
295 if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port)))
296 {
297 createSocketOverrideConf();
298 // Create override config file and write data.
299 std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName};
300 std::string tmpFile{ovrCfgFile + "_tmp"};
301 std::ofstream cfgFile(tmpFile, std::ios::out);
302 if (!cfgFile.good())
303 {
George Liucb267c82022-01-05 17:53:28 +0800304 lg2::error("Failed to open the {TMPFILE} file.", "TMPFILE",
305 tmpFile);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700306 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
307 Error::InternalFailure>();
308 }
309
310 // Write the socket header
311 cfgFile << "[Socket]\n";
312 // Listen
313 cfgFile << "Listen" << protocol << "="
314 << "\n";
315 cfgFile << "Listen" << protocol << "=" << portNum << "\n";
316 cfgFile.close();
317
318 if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
319 {
George Liucb267c82022-01-05 17:53:28 +0800320 lg2::error("Failed to rename {TMPFILE} file as {OVERCFGFILE} file.",
321 "TMPFILE", tmpFile, "OVERCFGFILE", ovrCfgFile);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700322 std::remove(tmpFile.c_str());
323 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
324 Error::InternalFailure>();
325 }
326 }
327
328 if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
329 (1 << static_cast<uint8_t>(UpdatedProp::enabledState))))
330 {
331 std::vector<std::string> unitFiles;
332 if (socketObjectPath.empty())
333 {
334 unitFiles = {getServiceUnitName()};
335 }
George Liua19b5092021-05-24 15:54:02 +0800336 else if (!socketObjectPath.empty() && isDropBearService)
337 {
338 unitFiles = {getSocketUnitName()};
339 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700340 else
341 {
342 unitFiles = {getSocketUnitName(), getServiceUnitName()};
343 }
344 systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue,
345 unitMaskedState, unitEnabledState);
346 }
347 return;
348}
349void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield)
350{
351 if (!updatedFlag || isMaskedOut())
352 {
353 // No updates. Just return.
354 return;
355 }
356
357 if (unitRunningState)
358 {
359 if (!socketObjectPath.empty())
360 {
361 systemdUnitAction(conn, yield, getSocketUnitName(),
362 sysdRestartUnit);
363 }
George Liua19b5092021-05-24 15:54:02 +0800364 if (!isDropBearService)
365 {
366 systemdUnitAction(conn, yield, getServiceUnitName(),
367 sysdRestartUnit);
368 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700369 }
370
371 // Reset the flag
372 updatedFlag = 0;
373
George Liucb267c82022-01-05 17:53:28 +0800374 lg2::info("Applied new settings: {OBJPATH}", "OBJPATH", objPath);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700375
376 queryAndUpdateProperties();
377 return;
378}
379
380void ServiceConfig::startServiceRestartTimer()
381{
382 timer->expires_after(std::chrono::seconds(restartTimeout));
383 timer->async_wait([this](const boost::system::error_code& ec) {
384 if (ec == boost::asio::error::operation_aborted)
385 {
386 // Timer reset.
387 return;
388 }
389 else if (ec)
390 {
George Liucb267c82022-01-05 17:53:28 +0800391 lg2::error("async wait error: {EC}", "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700392 return;
393 }
394 updateInProgress = true;
395 boost::asio::spawn(conn->get_io_context(),
396 [this](boost::asio::yield_context yield) {
397 // Stop and apply configuration for all objects
398 for (auto& srvMgrObj : srvMgrObjects)
399 {
400 auto& srvObj = srvMgrObj.second;
401 if (srvObj->updatedFlag)
402 {
403 srvObj->stopAndApplyUnitConfig(yield);
404 }
405 }
406 // Do system reload
407 systemdDaemonReload(conn, yield);
408 // restart unit config.
409 for (auto& srvMgrObj : srvMgrObjects)
410 {
411 auto& srvObj = srvMgrObj.second;
412 if (srvObj->updatedFlag)
413 {
414 srvObj->restartUnitConfig(yield);
415 }
416 }
417 updateInProgress = false;
418 });
419 });
420}
421
422void ServiceConfig::registerProperties()
423{
424 srvCfgIface = server.add_interface(objPath, serviceConfigIntfName);
425
426 if (!socketObjectPath.empty())
427 {
428 sockAttrIface = server.add_interface(objPath, sockAttrIntfName);
429 sockAttrIface->register_property(
430 sockAttrPropPort, portNum,
431 [this](const uint16_t& req, uint16_t& res) {
432 if (!internalSet)
433 {
434 if (req == res)
435 {
436 return 1;
437 }
438 if (updateInProgress)
439 {
440 return 0;
441 }
442 portNum = req;
443 updatedFlag |=
444 (1 << static_cast<uint8_t>(UpdatedProp::port));
445 startServiceRestartTimer();
446 }
447 res = req;
448 return 1;
449 });
450 }
451
452 srvCfgIface->register_property(
453 srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) {
454 if (!internalSet)
455 {
456 if (req == res)
457 {
458 return 1;
459 }
460 if (updateInProgress)
461 {
462 return 0;
463 }
464 unitMaskedState = req;
465 unitEnabledState = !unitMaskedState;
466 unitRunningState = !unitMaskedState;
467 updatedFlag |=
468 (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
469 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
470 (1 << static_cast<uint8_t>(UpdatedProp::runningState));
471 internalSet = true;
472 srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
473 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
474 internalSet = false;
475 startServiceRestartTimer();
476 }
477 res = req;
478 return 1;
479 });
480
481 srvCfgIface->register_property(
482 srvCfgPropEnabled, unitEnabledState,
483 [this](const bool& req, bool& res) {
484 if (!internalSet)
485 {
486 if (req == res)
487 {
488 return 1;
489 }
490 if (updateInProgress)
491 {
492 return 0;
493 }
494 if (unitMaskedState)
495 { // block updating if masked
George Liucb267c82022-01-05 17:53:28 +0800496 lg2::error("Invalid value specified");
Vernon Maueryba2c0832020-07-15 10:02:38 -0700497 return -EINVAL;
498 }
499 unitEnabledState = req;
500 updatedFlag |=
501 (1 << static_cast<uint8_t>(UpdatedProp::enabledState));
502 startServiceRestartTimer();
503 }
504 res = req;
505 return 1;
506 });
507
508 srvCfgIface->register_property(
509 srvCfgPropRunning, unitRunningState,
510 [this](const bool& req, bool& res) {
511 if (!internalSet)
512 {
513 if (req == res)
514 {
515 return 1;
516 }
517 if (updateInProgress)
518 {
519 return 0;
520 }
521 if (unitMaskedState)
522 { // block updating if masked
George Liucb267c82022-01-05 17:53:28 +0800523 lg2::error("Invalid value specified");
Vernon Maueryba2c0832020-07-15 10:02:38 -0700524 return -EINVAL;
525 }
526 unitRunningState = req;
527 updatedFlag |=
528 (1 << static_cast<uint8_t>(UpdatedProp::runningState));
529 startServiceRestartTimer();
530 }
531 res = req;
532 return 1;
533 });
534
535 srvCfgIface->initialize();
536 if (!socketObjectPath.empty())
537 {
538 sockAttrIface->initialize();
539 }
540 return;
541}
542
543} // namespace service
544} // namespace phosphor