blob: 333c0017e9b963610bdbd30e33da4eec3743810a [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 {
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530129 // Dropbear service expects template arguments.
AppaRao Puli00840472018-10-03 19:37:46 +0530130 srvUnitName.append("@");
131 }
132 srvUnitName.append(".service");
133 conn->async_method_call(
134 [this](boost::system::error_code ec, const std::string &pValue) {
135 if (ec)
136 {
137 phosphor::logging::log<phosphor::logging::level::ERR>(
138 "async_method_call error: Failed to get property");
139 return;
140 }
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530141 if ((pValue == "enabled") || (pValue == "static") ||
142 (pValue == "unmasked"))
143 {
144 stateValue = "enabled";
145 }
146 else if ((pValue == "disabled") || (pValue == "masked"))
147 {
148 stateValue = "disabled";
149 }
AppaRao Puli00840472018-10-03 19:37:46 +0530150 conn->async_method_call(
151 [](boost::system::error_code ec) {
152 if (ec)
153 {
154 phosphor::logging::log<phosphor::logging::level::ERR>(
155 "async_method_call error: Failed to set property");
156 return;
157 }
158 },
159 serviceConfigSrvName, objPath.c_str(),
160 "org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
161 "State", sdbusplus::message::variant<std::string>(stateValue));
162 },
163 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
164 "org.freedesktop.systemd1.Manager", "GetUnitFileState", srvUnitName);
165
166 return;
167}
168
169ServiceConfig::ServiceConfig(
170 sdbusplus::asio::object_server &srv_,
171 std::shared_ptr<sdbusplus::asio::connection> &conn_, std::string objPath_,
172 std::string unitName) :
173 server(srv_),
174 conn(conn_), objPath(objPath_), sysDUnitName(unitName)
175{
176 std::string socketUnitName(sysDUnitName + ".socket");
177 // .socket systemd service files are handled.
178 // Regular .service only files are ignored.
179 if (!checkSystemdUnitExist(socketUnitName))
180 {
181 phosphor::logging::log<phosphor::logging::level::ERR>(
182 "Unit doesn't exist.",
183 phosphor::logging::entry("UNITNAME=%s", socketUnitName.c_str()));
184 phosphor::logging::elog<
185 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
186 }
187
188 /// Check override socket directory exist, if not create it.
189 std::experimental::filesystem::path ovrUnitFileDir(
190 systemdOverrideUnitBasePath);
191 ovrUnitFileDir += socketUnitName;
192 ovrUnitFileDir += ".d";
193 if (!std::experimental::filesystem::exists(ovrUnitFileDir))
194 {
195 if (!std::experimental::filesystem::create_directories(ovrUnitFileDir))
196 {
197 phosphor::logging::log<phosphor::logging::level::ERR>(
198 "Unable to create the directory.",
199 phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
200 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
201 Error::InternalFailure>();
202 }
203 }
204
205 /* Store require info locally */
206 unitSocketFilePath = std::string(ovrUnitFileDir);
207
208 sysDSockObjPath = systemd1UnitBasePath;
209 sysDSockObjPath.append(
210 std::regex_replace(sysDUnitName, std::regex("-"), "_2d"));
211 sysDSockObjPath.append("_2esocket");
212
213 // Adds interface, object and Properties....
214 registerProperties();
215
216 syncWithSystemD1Properties();
217
218 updatedFlag = 0;
219 return;
220}
221
222void ServiceConfig::applySystemDServiceConfig()
223{
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530224 if (updatedFlag)
225 {
226 // No updates. Just return.
227 return;
228 }
229
AppaRao Puli00840472018-10-03 19:37:46 +0530230 phosphor::logging::log<phosphor::logging::level::INFO>(
231 "Applying new settings.",
232 phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
233 if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::channel)) |
234 (1 << static_cast<uint8_t>(UpdatedProp::port))))
235 {
236 // Create override config file and write data.
237 std::string ovrCfgFile{unitSocketFilePath + "/" + overrideConfFileName};
238 std::string tmpFile{ovrCfgFile + "_tmp"};
239 std::ofstream cfgFile(tmpFile, std::ios::out);
240 if (!cfgFile.good())
241 {
242 phosphor::logging::log<phosphor::logging::level::ERR>(
243 "Failed to open override.conf_tmp file");
244 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
245 Error::InternalFailure>();
246 }
247
248 // Write the socket header
249 cfgFile << "[Socket]\n";
250 // Listen
251 cfgFile << "Listen" << protocol << "="
252 << "\n";
253 cfgFile << "Listen" << protocol << "=" << portNum << "\n";
254 // BindToDevice
255 bool firstElement = true;
256 cfgFile << "BindToDevice=";
257 for (const auto &it : channelList)
258 {
259 if (firstElement)
260 {
261 cfgFile << it;
262 firstElement = false;
263 }
264 else
265 {
266 cfgFile << "," << it;
267 }
268 }
269 cfgFile.close();
270
271 if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
272 {
273 phosphor::logging::log<phosphor::logging::level::ERR>(
274 "Failed to rename tmp file as override.conf");
275 std::remove(tmpFile.c_str());
276 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
277 Error::InternalFailure>();
278 }
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530279 }
AppaRao Puli00840472018-10-03 19:37:46 +0530280
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530281 std::string socketUnitName(sysDUnitName + ".socket");
282 std::string srvUnitName(sysDUnitName);
283 if (srvUnitName == "dropbear")
284 {
285 // Dropbear service expects template arguments.
286 // Todo: Unit action for service, fails with error
287 // "missing the instance name". Needs to implement
288 // getting all running instances and use it. This
289 // impact runtime but works fine during reboot.
290 srvUnitName.append("@");
291 }
292 srvUnitName.append(".service");
293 // Stop the running service in below scenarios.
294 // 1. State changed from "enabled" to "disabled"
295 // 2. No change in state and existing stateValue is
296 // "enabled" and there is change in other properties.
297 if (((stateValue == "disabled") &&
298 (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state)))) ||
299 (!(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state))) &&
300 (stateValue == "enabled") && (updatedFlag)))
301 {
302 systemdUnitAction(conn, socketUnitName, "StopUnit");
303 systemdUnitAction(conn, srvUnitName, "StopUnit");
AppaRao Puli00840472018-10-03 19:37:46 +0530304 }
305
306 if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state)))
307 {
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530308 std::vector<std::string> unitFiles = {socketUnitName, srvUnitName};
309 systemdUnitFilesStateChange(conn, unitFiles, stateValue);
310 }
311
312 // Perform daemon reload to read new settings
313 systemdDaemonReload(conn);
314
315 if (stateValue == "enabled")
316 {
317 // Restart the socket
318 systemdUnitAction(conn, socketUnitName, "StartUnit");
AppaRao Puli00840472018-10-03 19:37:46 +0530319 }
320
321 // Reset the flag
322 updatedFlag = 0;
323
324 // All done. Lets reload the properties which are applied on systemd1.
325 // TODO: We need to capture the service restart signal and reload data
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530326 // inside the signal handler. So that we can update the service
327 // properties modified, outside of this service as well.
AppaRao Puli00840472018-10-03 19:37:46 +0530328 syncWithSystemD1Properties();
329
330 return;
331}
332
333void ServiceConfig::startServiceRestartTimer()
334{
335 timer->expires_from_now(boost::posix_time::seconds(restartTimeout));
336 timer->async_wait([this](const boost::system::error_code &ec) {
337 if (ec == boost::asio::error::operation_aborted)
338 {
339 // Timer reset.
340 return;
341 }
342 else if (ec)
343 {
344 phosphor::logging::log<phosphor::logging::level::ERR>(
345 "async wait error.");
346 return;
347 }
348 for (auto &srvMgrObj : srvMgrObjects)
349 {
350 auto &srvObj = srvMgrObj.second;
351 if (srvObj->updatedFlag)
352 {
353 srvObj->applySystemDServiceConfig();
354 }
355 }
356 });
357}
358
359void ServiceConfig::registerProperties()
360{
361 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
362 server.add_interface(objPath, serviceConfigIntfName);
363
364 iface->register_property(
365 "Port", portNum, [this](const uint16_t &req, uint16_t &res) {
AppaRao Puli00840472018-10-03 19:37:46 +0530366 if (req == res)
367 {
368 return 1;
369 }
370 portNum = req;
371 updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::port));
372 startServiceRestartTimer();
373 res = req;
374 return 1;
375 });
376
377 iface->register_property(
378 "Channel", channelList,
379 [this](const std::vector<std::string> &req,
380 std::vector<std::string> &res) {
381 if (req == res)
382 {
383 return 1;
384 }
385 channelList.clear();
386 std::copy(req.begin(), req.end(), back_inserter(channelList));
387
388 updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::channel));
389 startServiceRestartTimer();
390 res = req;
391 return 1;
392 });
393
394 iface->register_property(
395 "State", stateValue, [this](const std::string &req, std::string &res) {
396 if (req == res)
397 {
398 return 1;
399 }
AppaRao Pulie55cfd62019-02-15 15:35:29 +0530400 if ((req != "enabled") && (req != "disabled"))
AppaRao Puli00840472018-10-03 19:37:46 +0530401 {
402 phosphor::logging::log<phosphor::logging::level::ERR>(
403 "Invalid value specified");
404 return -EINVAL;
405 }
406 stateValue = req;
407 updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::state));
408 startServiceRestartTimer();
409 res = req;
410 return 1;
411 });
412
413 iface->initialize();
414 return;
415}
416
417} // namespace service
418} // namespace phosphor