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 (sockAttrIface && sockAttrIface->is_initialized())
62             {
63                 internalSet = true;
64                 sockAttrIface->set_property(sockAttrPropPort, 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 (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 
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 (!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 
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::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 
205 ServiceConfig::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 
222 std::string ServiceConfig::getSocketUnitName()
223 {
224     return instantiatedUnitName + ".socket";
225 }
226 
227 std::string ServiceConfig::getServiceUnitName()
228 {
229     return instantiatedUnitName + ".service";
230 }
231 
232 bool 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 
240 void 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 }
309 void 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 
338 void 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 
381 void 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
506