blob: 04d41caa30588cfdf228fac4d4951a7018c1a10b [file] [log] [blame]
AppaRao Puli00840472018-10-03 19:37:46 +05301/*
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
20extern std::shared_ptr<boost::asio::deadline_timer> timer;
21extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
22 srvMgrObjects;
23
24namespace phosphor
25{
26namespace service
27{
28
29static constexpr const char *overrideConfFileName = "override.conf";
30static constexpr const size_t restartTimeout = 15; // seconds
31
32static constexpr const char *systemd1UnitBasePath =
33 "/org/freedesktop/systemd1/unit/";
34static constexpr const char *systemdOverrideUnitBasePath =
35 "/etc/systemd/system/";
36
37void 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
160ServiceConfig::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
213void 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
300void 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
326void 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