xref: /openbmc/service-config-manager/src/srvcfg_manager.cpp (revision ee853eb2d865c7da9eec99cdcac04f8aee750e49)
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/asio/spawn.hpp>
19 
20 #include <fstream>
21 #include <regex>
22 
23 extern std::unique_ptr<boost::asio::steady_timer> timer;
24 extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
25     srvMgrObjects;
26 static bool updateInProgress = false;
27 
28 namespace phosphor
29 {
30 namespace service
31 {
32 
33 static constexpr const char* overrideConfFileName = "override.conf";
34 static constexpr const size_t restartTimeout = 15; // seconds
35 
36 static constexpr const char* systemd1UnitBasePath =
37     "/org/freedesktop/systemd1/unit/";
38 static constexpr const char* systemdOverrideUnitBasePath =
39     "/etc/systemd/system/";
40 
41 void 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 (iface && iface->is_initialized())
62             {
63                 internalSet = true;
64                 iface->set_property(srvCfgPropPort, portNum);
65                 internalSet = false;
66             }
67         }
68     }
69 }
70 
71 void 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 (iface && iface->is_initialized())
88         {
89             internalSet = true;
90             iface->set_property(srvCfgPropMasked, unitMaskedState);
91             iface->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 (iface && iface->is_initialized())
104         {
105             internalSet = true;
106             iface->set_property(srvCfgPropRunning, unitRunningState);
107             internalSet = false;
108         }
109     }
110 }
111 
112 void 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 (!iface)
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 (!iface)
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 
181 void 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::experimental::filesystem::path ovrUnitFileDir(
188             systemdOverrideUnitBasePath);
189         ovrUnitFileDir += socketUnitName;
190         ovrUnitFileDir += ".d";
191         if (!std::experimental::filesystem::exists(ovrUnitFileDir))
192         {
193             if (!std::experimental::filesystem::create_directories(
194                     ovrUnitFileDir))
195             {
196                 phosphor::logging::log<phosphor::logging::level::ERR>(
197                     "Unable to create the directory.",
198                     phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
199                 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
200                                             Common::Error::InternalFailure>();
201             }
202         }
203         overrideConfDir = std::string(ovrUnitFileDir);
204     }
205 }
206 
207 ServiceConfig::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     server(srv_),
214     conn(conn_), objPath(objPath_), baseUnitName(baseUnitName_),
215     instanceName(instanceName_), serviceObjectPath(serviceObjPath_),
216     socketObjectPath(socketObjPath_)
217 {
218     instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@");
219     updatedFlag = 0;
220     queryAndUpdateProperties();
221     return;
222 }
223 
224 std::string ServiceConfig::getSocketUnitName()
225 {
226     return instantiatedUnitName + ".socket";
227 }
228 
229 std::string ServiceConfig::getServiceUnitName()
230 {
231     return instantiatedUnitName + ".service";
232 }
233 
234 bool ServiceConfig::isMaskedOut()
235 {
236     // return true  if state is masked & no request to update the maskedState
237     return (
238         stateValue == "masked" &&
239         !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState))));
240 }
241 
242 void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield)
243 {
244     if (!updatedFlag || isMaskedOut())
245     {
246         // No updates / masked - Just return.
247         return;
248     }
249     phosphor::logging::log<phosphor::logging::level::INFO>(
250         "Applying new settings.",
251         phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
252     if (subStateValue == "running")
253     {
254         if (!socketObjectPath.empty())
255         {
256             systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
257         }
258         systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
259     }
260 
261     if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port)))
262     {
263         createSocketOverrideConf();
264         // Create override config file and write data.
265         std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName};
266         std::string tmpFile{ovrCfgFile + "_tmp"};
267         std::ofstream cfgFile(tmpFile, std::ios::out);
268         if (!cfgFile.good())
269         {
270             phosphor::logging::log<phosphor::logging::level::ERR>(
271                 "Failed to open override.conf_tmp file");
272             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
273                                         Error::InternalFailure>();
274         }
275 
276         // Write the socket header
277         cfgFile << "[Socket]\n";
278         // Listen
279         cfgFile << "Listen" << protocol << "="
280                 << "\n";
281         cfgFile << "Listen" << protocol << "=" << portNum << "\n";
282         cfgFile.close();
283 
284         if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
285         {
286             phosphor::logging::log<phosphor::logging::level::ERR>(
287                 "Failed to rename tmp file as override.conf");
288             std::remove(tmpFile.c_str());
289             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
290                                         Error::InternalFailure>();
291         }
292     }
293 
294     if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
295                        (1 << static_cast<uint8_t>(UpdatedProp::enabledState))))
296     {
297         std::vector<std::string> unitFiles;
298         if (socketObjectPath.empty())
299         {
300             unitFiles = {getServiceUnitName()};
301         }
302         else
303         {
304             unitFiles = {getSocketUnitName(), getServiceUnitName()};
305         }
306         systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue,
307                                     unitMaskedState, unitEnabledState);
308     }
309     return;
310 }
311 void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield)
312 {
313     if (!updatedFlag || isMaskedOut())
314     {
315         // No updates. Just return.
316         return;
317     }
318 
319     if (unitRunningState)
320     {
321         if (!socketObjectPath.empty())
322         {
323             systemdUnitAction(conn, yield, getSocketUnitName(),
324                               sysdRestartUnit);
325         }
326         systemdUnitAction(conn, yield, getServiceUnitName(), sysdRestartUnit);
327     }
328 
329     // Reset the flag
330     updatedFlag = 0;
331 
332     phosphor::logging::log<phosphor::logging::level::INFO>(
333         "Applied new settings",
334         phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
335 
336     queryAndUpdateProperties();
337     return;
338 }
339 
340 void ServiceConfig::startServiceRestartTimer()
341 {
342     timer->expires_after(std::chrono::seconds(restartTimeout));
343     timer->async_wait([this](const boost::system::error_code& ec) {
344         if (ec == boost::asio::error::operation_aborted)
345         {
346             // Timer reset.
347             return;
348         }
349         else if (ec)
350         {
351             phosphor::logging::log<phosphor::logging::level::ERR>(
352                 "async wait error.");
353             return;
354         }
355         updateInProgress = true;
356         boost::asio::spawn(conn->get_io_context(),
357                            [this](boost::asio::yield_context yield) {
358                                // Stop and apply configuration for all objects
359                                for (auto& srvMgrObj : srvMgrObjects)
360                                {
361                                    auto& srvObj = srvMgrObj.second;
362                                    if (srvObj->updatedFlag)
363                                    {
364                                        srvObj->stopAndApplyUnitConfig(yield);
365                                    }
366                                }
367                                // Do system reload
368                                systemdDaemonReload(conn, yield);
369                                // restart unit config.
370                                for (auto& srvMgrObj : srvMgrObjects)
371                                {
372                                    auto& srvObj = srvMgrObj.second;
373                                    if (srvObj->updatedFlag)
374                                    {
375                                        srvObj->restartUnitConfig(yield);
376                                    }
377                                }
378                                updateInProgress = false;
379                            });
380     });
381 }
382 
383 void ServiceConfig::registerProperties()
384 {
385     iface = server.add_interface(objPath, serviceConfigIntfName);
386 
387     if (!socketObjectPath.empty())
388     {
389         iface->register_property(
390             srvCfgPropPort, portNum,
391             [this](const uint16_t& req, uint16_t& res) {
392                 if (!internalSet)
393                 {
394                     if (req == res)
395                     {
396                         return 1;
397                     }
398                     if (updateInProgress)
399                     {
400                         return 0;
401                     }
402                     portNum = req;
403                     updatedFlag |=
404                         (1 << static_cast<uint8_t>(UpdatedProp::port));
405                     startServiceRestartTimer();
406                 }
407                 res = req;
408                 return 1;
409             });
410     }
411 
412     iface->register_property(
413         srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) {
414             if (!internalSet)
415             {
416                 if (req == res)
417                 {
418                     return 1;
419                 }
420                 if (updateInProgress)
421                 {
422                     return 0;
423                 }
424                 unitMaskedState = req;
425                 unitEnabledState = !unitMaskedState;
426                 unitRunningState = !unitMaskedState;
427                 updatedFlag |=
428                     (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
429                     (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
430                     (1 << static_cast<uint8_t>(UpdatedProp::runningState));
431                 internalSet = true;
432                 iface->set_property(srvCfgPropEnabled, unitEnabledState);
433                 iface->set_property(srvCfgPropRunning, unitRunningState);
434                 internalSet = false;
435                 startServiceRestartTimer();
436             }
437             res = req;
438             return 1;
439         });
440 
441     iface->register_property(
442         srvCfgPropEnabled, unitEnabledState,
443         [this](const bool& req, bool& res) {
444             if (!internalSet)
445             {
446                 if (req == res)
447                 {
448                     return 1;
449                 }
450                 if (updateInProgress)
451                 {
452                     return 0;
453                 }
454                 if (unitMaskedState)
455                 { // block updating if masked
456                     phosphor::logging::log<phosphor::logging::level::ERR>(
457                         "Invalid value specified");
458                     return -EINVAL;
459                 }
460                 unitEnabledState = req;
461                 updatedFlag |=
462                     (1 << static_cast<uint8_t>(UpdatedProp::enabledState));
463                 startServiceRestartTimer();
464             }
465             res = req;
466             return 1;
467         });
468 
469     iface->register_property(
470         srvCfgPropRunning, unitRunningState,
471         [this](const bool& req, bool& res) {
472             if (!internalSet)
473             {
474                 if (req == res)
475                 {
476                     return 1;
477                 }
478                 if (updateInProgress)
479                 {
480                     return 0;
481                 }
482                 if (unitMaskedState)
483                 { // block updating if masked
484                     phosphor::logging::log<phosphor::logging::level::ERR>(
485                         "Invalid value specified");
486                     return -EINVAL;
487                 }
488                 unitRunningState = req;
489                 updatedFlag |=
490                     (1 << static_cast<uint8_t>(UpdatedProp::runningState));
491                 startServiceRestartTimer();
492             }
493             res = req;
494             return 1;
495         });
496 
497     iface->initialize();
498     return;
499 }
500 
501 } // namespace service
502 } // namespace phosphor
503