xref: /openbmc/service-config-manager/src/srvcfg_manager.cpp (revision 0084047d008fd0ac36f09a232f67ff2fc5314b53)
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 <fstream>
17 #include <regex>
18 #include "srvcfg_manager.hpp"
19 
20 extern std::shared_ptr<boost::asio::deadline_timer> timer;
21 extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
22     srvMgrObjects;
23 
24 namespace phosphor
25 {
26 namespace service
27 {
28 
29 static constexpr const char *overrideConfFileName = "override.conf";
30 static constexpr const size_t restartTimeout = 15; // seconds
31 
32 static constexpr const char *systemd1UnitBasePath =
33     "/org/freedesktop/systemd1/unit/";
34 static constexpr const char *systemdOverrideUnitBasePath =
35     "/etc/systemd/system/";
36 
37 void ServiceConfig::syncWithSystemD1Properties()
38 {
39     // Read systemd1 socket/service property and load.
40     conn->async_method_call(
41         [this](boost::system::error_code ec,
42                const sdbusplus::message::variant<
43                    std::vector<std::tuple<std::string, std::string>>> &value) {
44             if (ec)
45             {
46                 phosphor::logging::log<phosphor::logging::level::ERR>(
47                     "async_method_call error: Failed to get property");
48                 return;
49             }
50 
51             try
52             {
53                 auto listenVal = sdbusplus::message::variant_ns::get<
54                     std::vector<std::tuple<std::string, std::string>>>(value);
55                 protocol = std::get<0>(listenVal[0]);
56                 std::string port = std::get<1>(listenVal[0]);
57                 auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1),
58                                       nullptr, 10);
59                 if (tmp > std::numeric_limits<uint16_t>::max())
60                 {
61                     throw std::out_of_range("Out of range");
62                 }
63                 portNum = tmp;
64             }
65             catch (const std::exception &e)
66             {
67                 phosphor::logging::log<phosphor::logging::level::ERR>(
68                     "Exception for port number",
69                     phosphor::logging::entry("WHAT=%s", e.what()));
70                 return;
71             }
72             conn->async_method_call(
73                 [](boost::system::error_code ec) {
74                     if (ec)
75                     {
76                         phosphor::logging::log<phosphor::logging::level::ERR>(
77                             "async_method_call error: Failed to set property");
78                         return;
79                     }
80                 },
81                 serviceConfigSrvName, objPath.c_str(),
82                 "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
83                 "Port", sdbusplus::message::variant<uint16_t>(portNum));
84         },
85         "org.freedesktop.systemd1", sysDSockObjPath.c_str(),
86         "org.freedesktop.DBus.Properties", "Get",
87         "org.freedesktop.systemd1.Socket", "Listen");
88 
89     conn->async_method_call(
90         [this](boost::system::error_code ec,
91                const sdbusplus::message::variant<std::string> &pValue) {
92             if (ec)
93             {
94                 phosphor::logging::log<phosphor::logging::level::ERR>(
95                     "async_method_call error: Failed to get property");
96                 return;
97             }
98 
99             channelList.clear();
100             std::istringstream stm(
101                 sdbusplus::message::variant_ns::get<std::string>(pValue));
102             std::string token;
103             while (std::getline(stm, token, ','))
104             {
105                 channelList.push_back(token);
106             }
107             conn->async_method_call(
108                 [](boost::system::error_code ec) {
109                     if (ec)
110                     {
111                         phosphor::logging::log<phosphor::logging::level::ERR>(
112                             "async_method_call error: Failed to set property");
113                         return;
114                     }
115                 },
116                 serviceConfigSrvName, objPath.c_str(),
117                 "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
118                 "Channel",
119                 sdbusplus::message::variant<std::vector<std::string>>(
120                     channelList));
121         },
122         "org.freedesktop.systemd1", sysDSockObjPath.c_str(),
123         "org.freedesktop.DBus.Properties", "Get",
124         "org.freedesktop.systemd1.Socket", "BindToDevice");
125 
126     std::string srvUnitName(sysDUnitName);
127     if (srvUnitName == "dropbear")
128     {
129         srvUnitName.append("@");
130     }
131     srvUnitName.append(".service");
132     conn->async_method_call(
133         [this](boost::system::error_code ec, const std::string &pValue) {
134             if (ec)
135             {
136                 phosphor::logging::log<phosphor::logging::level::ERR>(
137                     "async_method_call error: Failed to get property");
138                 return;
139             }
140             stateValue = pValue;
141             conn->async_method_call(
142                 [](boost::system::error_code ec) {
143                     if (ec)
144                     {
145                         phosphor::logging::log<phosphor::logging::level::ERR>(
146                             "async_method_call error: Failed to set property");
147                         return;
148                     }
149                 },
150                 serviceConfigSrvName, objPath.c_str(),
151                 "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
152                 "State", sdbusplus::message::variant<std::string>(stateValue));
153         },
154         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
155         "org.freedesktop.systemd1.Manager", "GetUnitFileState", srvUnitName);
156 
157     return;
158 }
159 
160 ServiceConfig::ServiceConfig(
161     sdbusplus::asio::object_server &srv_,
162     std::shared_ptr<sdbusplus::asio::connection> &conn_, std::string objPath_,
163     std::string unitName) :
164     server(srv_),
165     conn(conn_), objPath(objPath_), sysDUnitName(unitName)
166 {
167     std::string socketUnitName(sysDUnitName + ".socket");
168     // .socket systemd service files are handled.
169     // Regular .service only files are ignored.
170     if (!checkSystemdUnitExist(socketUnitName))
171     {
172         phosphor::logging::log<phosphor::logging::level::ERR>(
173             "Unit doesn't exist.",
174             phosphor::logging::entry("UNITNAME=%s", socketUnitName.c_str()));
175         phosphor::logging::elog<
176             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
177     }
178 
179     /// Check override socket directory exist, if not create it.
180     std::experimental::filesystem::path ovrUnitFileDir(
181         systemdOverrideUnitBasePath);
182     ovrUnitFileDir += socketUnitName;
183     ovrUnitFileDir += ".d";
184     if (!std::experimental::filesystem::exists(ovrUnitFileDir))
185     {
186         if (!std::experimental::filesystem::create_directories(ovrUnitFileDir))
187         {
188             phosphor::logging::log<phosphor::logging::level::ERR>(
189                 "Unable to create the directory.",
190                 phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
191             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
192                                         Error::InternalFailure>();
193         }
194     }
195 
196     /* Store require info locally */
197     unitSocketFilePath = std::string(ovrUnitFileDir);
198 
199     sysDSockObjPath = systemd1UnitBasePath;
200     sysDSockObjPath.append(
201         std::regex_replace(sysDUnitName, std::regex("-"), "_2d"));
202     sysDSockObjPath.append("_2esocket");
203 
204     // Adds interface, object and Properties....
205     registerProperties();
206 
207     syncWithSystemD1Properties();
208 
209     updatedFlag = 0;
210     return;
211 }
212 
213 void ServiceConfig::applySystemDServiceConfig()
214 {
215     phosphor::logging::log<phosphor::logging::level::INFO>(
216         "Applying new settings.",
217         phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
218     if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::channel)) |
219                        (1 << static_cast<uint8_t>(UpdatedProp::port))))
220     {
221         // Create override config file and write data.
222         std::string ovrCfgFile{unitSocketFilePath + "/" + overrideConfFileName};
223         std::string tmpFile{ovrCfgFile + "_tmp"};
224         std::ofstream cfgFile(tmpFile, std::ios::out);
225         if (!cfgFile.good())
226         {
227             phosphor::logging::log<phosphor::logging::level::ERR>(
228                 "Failed to open override.conf_tmp file");
229             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
230                                         Error::InternalFailure>();
231         }
232 
233         // Write the socket header
234         cfgFile << "[Socket]\n";
235         // Listen
236         cfgFile << "Listen" << protocol << "="
237                 << "\n";
238         cfgFile << "Listen" << protocol << "=" << portNum << "\n";
239         // BindToDevice
240         bool firstElement = true;
241         cfgFile << "BindToDevice=";
242         for (const auto &it : channelList)
243         {
244             if (firstElement)
245             {
246                 cfgFile << it;
247                 firstElement = false;
248             }
249             else
250             {
251                 cfgFile << "," << it;
252             }
253         }
254         cfgFile.close();
255 
256         if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
257         {
258             phosphor::logging::log<phosphor::logging::level::ERR>(
259                 "Failed to rename tmp file as override.conf");
260             std::remove(tmpFile.c_str());
261             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
262                                         Error::InternalFailure>();
263         }
264 
265         // Systemd forcing explicit socket stop before reload...!
266         std::string socketUnitName(sysDUnitName + ".socket");
267         systemdUnitAction(conn, socketUnitName, sysdActionStopUnit);
268 
269         std::string srvUnitName(sysDUnitName + ".service");
270         systemdUnitAction(conn, srvUnitName, sysdActionStopUnit);
271 
272         // Perform daemon reload to read new settings
273         systemdDaemonReload(conn);
274 
275         // Restart the unit
276         systemdUnitAction(conn, socketUnitName, sysdActionStartUnit);
277         systemdUnitAction(conn, srvUnitName, sysdActionStartUnit);
278     }
279 
280     if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state)))
281     {
282         if ((stateValue == "enabled") || (stateValue == "disabled"))
283         {
284             systemdUnitFileStateChange(conn, sysDUnitName, stateValue);
285         }
286     }
287 
288     // Reset the flag
289     updatedFlag = 0;
290 
291     // All done. Lets reload the properties which are applied on systemd1.
292     // TODO: We need to capture the service restart signal and reload data
293     // inside the signal handler. So that we can update the service properties
294     // modified, outside of this service as well.
295     syncWithSystemD1Properties();
296 
297     return;
298 }
299 
300 void ServiceConfig::startServiceRestartTimer()
301 {
302     timer->expires_from_now(boost::posix_time::seconds(restartTimeout));
303     timer->async_wait([this](const boost::system::error_code &ec) {
304         if (ec == boost::asio::error::operation_aborted)
305         {
306             // Timer reset.
307             return;
308         }
309         else if (ec)
310         {
311             phosphor::logging::log<phosphor::logging::level::ERR>(
312                 "async wait error.");
313             return;
314         }
315         for (auto &srvMgrObj : srvMgrObjects)
316         {
317             auto &srvObj = srvMgrObj.second;
318             if (srvObj->updatedFlag)
319             {
320                 srvObj->applySystemDServiceConfig();
321             }
322         }
323     });
324 }
325 
326 void ServiceConfig::registerProperties()
327 {
328     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
329         server.add_interface(objPath, serviceConfigIntfName);
330 
331     iface->register_property(
332         "Port", portNum, [this](const uint16_t &req, uint16_t &res) {
333             phosphor::logging::log<phosphor::logging::level::ERR>(
334                 " Inside register_property");
335             if (req == res)
336             {
337                 return 1;
338             }
339             portNum = req;
340             updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::port));
341             startServiceRestartTimer();
342             res = req;
343             return 1;
344         });
345 
346     iface->register_property(
347         "Channel", channelList,
348         [this](const std::vector<std::string> &req,
349                std::vector<std::string> &res) {
350             if (req == res)
351             {
352                 return 1;
353             }
354             channelList.clear();
355             std::copy(req.begin(), req.end(), back_inserter(channelList));
356 
357             updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::channel));
358             startServiceRestartTimer();
359             res = req;
360             return 1;
361         });
362 
363     iface->register_property(
364         "State", stateValue, [this](const std::string &req, std::string &res) {
365             if (req == res)
366             {
367                 return 1;
368             }
369             if ((req != "enabled") && (req != "disabled") && (req != "static"))
370             {
371                 phosphor::logging::log<phosphor::logging::level::ERR>(
372                     "Invalid value specified");
373                 return -EINVAL;
374             }
375             stateValue = req;
376             updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::state));
377             startServiceRestartTimer();
378             res = req;
379             return 1;
380         });
381 
382     iface->initialize();
383     return;
384 }
385 
386 } // namespace service
387 } // namespace phosphor
388