blob: 1d9ebed1529d924a55ffc42e9a97d4e70e85d072 [file] [log] [blame]
Vernon Maueryba2c0832020-07-15 10:02:38 -07001/*
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/algorithm/string/replace.hpp>
19#include <cereal/archives/json.hpp>
20#include <cereal/types/tuple.hpp>
21#include <cereal/types/unordered_map.hpp>
22#include <sdbusplus/bus/match.hpp>
23
24#include <filesystem>
25#include <fstream>
26
27std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
28std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
29std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
30 srvMgrObjects;
31static bool unitQueryStarted = false;
32
33static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
34
35// Base service name list. All instance of these services and
36// units(service/socket) will be managed by this daemon.
George Liua19b5092021-05-24 15:54:02 +080037static std::array<std::string, 6> serviceNames = {
38 "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs",
39 "start-ipkvm", "obmc-console", "dropbear"};
Vernon Maueryba2c0832020-07-15 10:02:38 -070040
41enum class UnitType
42{
43 service,
44 socket,
45 target,
46 device,
47 invalid
48};
49
50using MonitorListMap =
51 std::unordered_map<std::string, std::tuple<std::string, std::string,
52 std::string, std::string>>;
53MonitorListMap unitsToMonitor;
54
55enum class monitorElement
56{
57 unitName,
58 instanceName,
59 serviceObjPath,
60 socketObjPath
61};
62
63std::tuple<std::string, UnitType, std::string>
64 getUnitNameTypeAndInstance(const std::string& fullUnitName)
65{
66 UnitType type = UnitType::invalid;
67 std::string instanceName;
68 std::string unitName;
69 // get service type
70 auto typePos = fullUnitName.rfind(".");
71 if (typePos != std::string::npos)
72 {
73 const auto& typeStr = fullUnitName.substr(typePos + 1);
74 // Ignore types other than service and socket
75 if (typeStr == "service")
76 {
77 type = UnitType::service;
78 }
79 else if (typeStr == "socket")
80 {
81 type = UnitType::socket;
82 }
83 // get instance name if available
84 auto instancePos = fullUnitName.rfind("@");
85 if (instancePos != std::string::npos)
86 {
87 instanceName =
88 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
89 unitName = fullUnitName.substr(0, instancePos);
90 }
91 else
92 {
93 unitName = fullUnitName.substr(0, typePos);
94 }
95 }
96 return std::make_tuple(unitName, type, instanceName);
97}
98
99static inline void
100 handleListUnitsResponse(sdbusplus::asio::object_server& server,
101 std::shared_ptr<sdbusplus::asio::connection>& conn,
102 boost::system::error_code /*ec*/,
103 const std::vector<ListUnitsType>& listUnits)
104{
105 // Loop through all units, and mark all units, which has to be
106 // managed, irrespective of instance name.
107 for (const auto& unit : listUnits)
108 {
109 const auto& fullUnitName =
110 std::get<static_cast<int>(ListUnitElements::name)>(unit);
111 auto [unitName, type, instanceName] =
112 getUnitNameTypeAndInstance(fullUnitName);
113 if (std::find(serviceNames.begin(), serviceNames.end(), unitName) !=
114 serviceNames.end())
115 {
116 std::string instantiatedUnitName =
117 unitName + addInstanceName(instanceName, "_40");
118 boost::replace_all(instantiatedUnitName, "-", "_2d");
119 const sdbusplus::message::object_path& objectPath =
120 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
121 // Group the service & socket units togther.. Same services
122 // are managed together.
123 auto it = unitsToMonitor.find(instantiatedUnitName);
124 if (it != unitsToMonitor.end())
125 {
126 auto& value = it->second;
127 if (type == UnitType::service)
128 {
129 std::get<static_cast<int>(monitorElement::unitName)>(
130 value) = unitName;
131 std::get<static_cast<int>(monitorElement::instanceName)>(
132 value) = instanceName;
133 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
134 value) = objectPath;
135 }
136 else if (type == UnitType::socket)
137 {
138 std::get<static_cast<int>(monitorElement::socketObjPath)>(
139 value) = objectPath;
140 }
141 }
142 if (type == UnitType::service)
143 {
144 unitsToMonitor.emplace(instantiatedUnitName,
145 std::make_tuple(unitName, instanceName,
146 objectPath.str, ""));
147 }
148 else if (type == UnitType::socket)
149 {
150 unitsToMonitor.emplace(
151 instantiatedUnitName,
152 std::make_tuple("", "", "", objectPath.str));
153 }
154 }
155 }
156
157 bool updateRequired = false;
158 bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
159 if (jsonExist)
160 {
161 std::ifstream file(srvCfgMgrFile);
162 cereal::JSONInputArchive archive(file);
163 MonitorListMap savedMonitorList;
164 archive(savedMonitorList);
165
166 // compare the unit list read from systemd1 and the save list.
167 MonitorListMap diffMap;
168 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
169 begin(savedMonitorList), end(savedMonitorList),
170 std::inserter(diffMap, begin(diffMap)));
171 for (auto& unitIt : diffMap)
172 {
173 auto it = savedMonitorList.find(unitIt.first);
174 if (it == savedMonitorList.end())
175 {
176 savedMonitorList.insert(unitIt);
177 updateRequired = true;
178 }
179 }
180 unitsToMonitor = savedMonitorList;
181 }
182 if (!jsonExist || updateRequired)
183 {
184 std::ofstream file(srvCfgMgrFile);
185 cereal::JSONOutputArchive archive(file);
186 archive(CEREAL_NVP(unitsToMonitor));
187 }
188
189 // create objects for needed services
190 for (auto& it : unitsToMonitor)
191 {
192 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
193 "/" + it.first);
194 std::string instanciatedUnitName =
195 std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
196 addInstanceName(
197 std::get<static_cast<int>(monitorElement::instanceName)>(
198 it.second),
199 "@");
200 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
201 server, conn, objPath,
202 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
203 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
204 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
205 it.second),
206 std::get<static_cast<int>(monitorElement::socketObjPath)>(
207 it.second));
208 srvMgrObjects.emplace(
209 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
210 }
211}
212
213void init(sdbusplus::asio::object_server& server,
214 std::shared_ptr<sdbusplus::asio::connection>& conn)
215{
216 // Go through all systemd units, and dynamically detect and manage
217 // the service daemons
218 conn->async_method_call(
219 [&server, &conn](boost::system::error_code ec,
220 const std::vector<ListUnitsType>& listUnits) {
221 if (ec)
222 {
223 phosphor::logging::log<phosphor::logging::level::ERR>(
224 "async_method_call error: ListUnits failed");
225 return;
226 }
227 handleListUnitsResponse(server, conn, ec, listUnits);
228 },
229 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
230}
231
232void checkAndInit(sdbusplus::asio::object_server& server,
233 std::shared_ptr<sdbusplus::asio::connection>& conn)
234{
235 // Check whether systemd completed all the loading before initializing
236 conn->async_method_call(
237 [&server, &conn](boost::system::error_code ec,
238 const std::variant<uint64_t>& value) {
239 if (ec)
240 {
241 phosphor::logging::log<phosphor::logging::level::ERR>(
242 "async_method_call error: ListUnits failed");
243 return;
244 }
245 if (std::get<uint64_t>(value))
246 {
247 if (!unitQueryStarted)
248 {
249 unitQueryStarted = true;
250 init(server, conn);
251 }
252 }
253 else
254 {
255 // FIX-ME: Latest up-stream sync caused issue in receiving
256 // StartupFinished signal. Unable to get StartupFinished signal
257 // from systemd1 hence using poll method too, to trigger it
258 // properly.
259 constexpr size_t pollTimeout = 10; // seconds
260 initTimer->expires_after(std::chrono::seconds(pollTimeout));
261 initTimer->async_wait([&server, &conn](
262 const boost::system::error_code& ec) {
263 if (ec == boost::asio::error::operation_aborted)
264 {
265 // Timer reset.
266 return;
267 }
268 if (ec)
269 {
270 phosphor::logging::log<phosphor::logging::level::ERR>(
271 "service config mgr - init - async wait error.");
272 return;
273 }
274 checkAndInit(server, conn);
275 });
276 }
277 },
278 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
279 "FinishTimestamp");
280}
281
282int main()
283{
284 boost::asio::io_service io;
285 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
286 timer = std::make_unique<boost::asio::steady_timer>(io);
287 initTimer = std::make_unique<boost::asio::steady_timer>(io);
288 conn->request_name(phosphor::service::serviceConfigSrvName);
289 auto server = sdbusplus::asio::object_server(conn, true);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700290 server.add_manager(phosphor::service::srcCfgMgrBasePath);
291 // Initialize the objects after systemd indicated startup finished.
292 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
293 static_cast<sdbusplus::bus::bus&>(*conn),
294 "type='signal',"
295 "member='StartupFinished',path='/org/freedesktop/systemd1',"
296 "interface='org.freedesktop.systemd1.Manager'",
297 [&server, &conn](sdbusplus::message::message& /*msg*/) {
298 if (!unitQueryStarted)
299 {
300 unitQueryStarted = true;
301 init(server, conn);
302 }
303 });
304 // this will make sure to initialize the objects, when daemon is
305 // restarted.
306 checkAndInit(server, conn);
307
308 io.run();
309
310 return 0;
311}