blob: 3d678008908264454d4beec4ab0d5979d8eae0e9 [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";
George Liuf2744892022-01-05 17:54:45 +080034static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
Vernon Maueryba2c0832020-07-15 10:02:38 -070035
36// Base service name list. All instance of these services and
37// units(service/socket) will be managed by this daemon.
George Liua19b5092021-05-24 15:54:02 +080038static std::array<std::string, 6> serviceNames = {
39 "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs",
40 "start-ipkvm", "obmc-console", "dropbear"};
Vernon Maueryba2c0832020-07-15 10:02:38 -070041
42enum class UnitType
43{
44 service,
45 socket,
46 target,
47 device,
48 invalid
49};
50
51using MonitorListMap =
52 std::unordered_map<std::string, std::tuple<std::string, std::string,
53 std::string, std::string>>;
54MonitorListMap unitsToMonitor;
55
56enum class monitorElement
57{
58 unitName,
59 instanceName,
60 serviceObjPath,
61 socketObjPath
62};
63
64std::tuple<std::string, UnitType, std::string>
65 getUnitNameTypeAndInstance(const std::string& fullUnitName)
66{
67 UnitType type = UnitType::invalid;
68 std::string instanceName;
69 std::string unitName;
70 // get service type
71 auto typePos = fullUnitName.rfind(".");
72 if (typePos != std::string::npos)
73 {
74 const auto& typeStr = fullUnitName.substr(typePos + 1);
75 // Ignore types other than service and socket
76 if (typeStr == "service")
77 {
78 type = UnitType::service;
79 }
80 else if (typeStr == "socket")
81 {
82 type = UnitType::socket;
83 }
84 // get instance name if available
85 auto instancePos = fullUnitName.rfind("@");
86 if (instancePos != std::string::npos)
87 {
88 instanceName =
89 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
90 unitName = fullUnitName.substr(0, instancePos);
91 }
92 else
93 {
94 unitName = fullUnitName.substr(0, typePos);
95 }
96 }
97 return std::make_tuple(unitName, type, instanceName);
98}
99
100static inline void
101 handleListUnitsResponse(sdbusplus::asio::object_server& server,
102 std::shared_ptr<sdbusplus::asio::connection>& conn,
103 boost::system::error_code /*ec*/,
104 const std::vector<ListUnitsType>& listUnits)
105{
106 // Loop through all units, and mark all units, which has to be
107 // managed, irrespective of instance name.
108 for (const auto& unit : listUnits)
109 {
Jiaqing Zhaod2a99a42022-02-25 17:26:30 +0800110 // Ignore non-existent units
111 if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
112 loadStateNotFound)
113 {
114 continue;
115 }
116
Vernon Maueryba2c0832020-07-15 10:02:38 -0700117 const auto& fullUnitName =
118 std::get<static_cast<int>(ListUnitElements::name)>(unit);
119 auto [unitName, type, instanceName] =
120 getUnitNameTypeAndInstance(fullUnitName);
121 if (std::find(serviceNames.begin(), serviceNames.end(), unitName) !=
122 serviceNames.end())
123 {
124 std::string instantiatedUnitName =
125 unitName + addInstanceName(instanceName, "_40");
126 boost::replace_all(instantiatedUnitName, "-", "_2d");
127 const sdbusplus::message::object_path& objectPath =
128 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
129 // Group the service & socket units togther.. Same services
130 // are managed together.
131 auto it = unitsToMonitor.find(instantiatedUnitName);
132 if (it != unitsToMonitor.end())
133 {
134 auto& value = it->second;
135 if (type == UnitType::service)
136 {
Vernon Maueryba2c0832020-07-15 10:02:38 -0700137 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800138 value) = objectPath.str;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700139 }
140 else if (type == UnitType::socket)
141 {
142 std::get<static_cast<int>(monitorElement::socketObjPath)>(
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800143 value) = objectPath.str;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700144 }
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800145 continue;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700146 }
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800147 // If not grouped with any existing entry, create a new one
Vernon Maueryba2c0832020-07-15 10:02:38 -0700148 if (type == UnitType::service)
149 {
150 unitsToMonitor.emplace(instantiatedUnitName,
151 std::make_tuple(unitName, instanceName,
152 objectPath.str, ""));
153 }
154 else if (type == UnitType::socket)
155 {
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800156 unitsToMonitor.emplace(instantiatedUnitName,
157 std::make_tuple(unitName, instanceName,
158 "", objectPath.str));
Vernon Maueryba2c0832020-07-15 10:02:38 -0700159 }
160 }
161 }
162
163 bool updateRequired = false;
164 bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
165 if (jsonExist)
166 {
George Liuf2744892022-01-05 17:54:45 +0800167 try
Vernon Maueryba2c0832020-07-15 10:02:38 -0700168 {
George Liuf2744892022-01-05 17:54:45 +0800169 std::ifstream file(srvCfgMgrFile);
170 cereal::JSONInputArchive archive(file);
171 MonitorListMap savedMonitorList;
172 archive(savedMonitorList);
173
174 // compare the unit list read from systemd1 and the save list.
175 MonitorListMap diffMap;
176 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
177 begin(savedMonitorList), end(savedMonitorList),
178 std::inserter(diffMap, begin(diffMap)));
179 for (auto& unitIt : diffMap)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700180 {
George Liuf2744892022-01-05 17:54:45 +0800181 auto it = savedMonitorList.find(unitIt.first);
182 if (it == savedMonitorList.end())
183 {
184 savedMonitorList.insert(unitIt);
185 updateRequired = true;
186 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700187 }
George Liuf2744892022-01-05 17:54:45 +0800188 unitsToMonitor = savedMonitorList;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700189 }
George Liuf2744892022-01-05 17:54:45 +0800190 catch (const std::exception& e)
191 {
192 lg2::error(
193 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
194 "FILEPATH", srvCfgMgrFile, "ERROR", e);
195
196 // The "bad" files need to be moved to /tmp/ so that we can try to
197 // find out the cause of the file corruption. If we encounter this
198 // failure multiple times, we will only overwrite it to ensure that
199 // we don't accidentally fill up /tmp/.
200 std::error_code ec;
201 std::filesystem::copy_file(
202 srvCfgMgrFile, tmpFileBad,
203 std::filesystem::copy_options::overwrite_existing, ec);
204 if (ec)
205 {
206 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
207 "SRCFILE", srvCfgMgrFile, "DSTFILE", tmpFileBad);
208 }
209
210 updateRequired = true;
211 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700212 }
213 if (!jsonExist || updateRequired)
214 {
215 std::ofstream file(srvCfgMgrFile);
216 cereal::JSONOutputArchive archive(file);
217 archive(CEREAL_NVP(unitsToMonitor));
218 }
219
Chicago Duan25a0f632021-11-11 16:32:07 +0800220#ifdef USB_CODE_UPDATE
221 unitsToMonitor.emplace(
222 "phosphor_2dusb_2dcode_2dupdate",
223 std::make_tuple(
224 "phosphor_usb_code_update", "",
225 "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
226 ""));
227#endif
228
Vernon Maueryba2c0832020-07-15 10:02:38 -0700229 // create objects for needed services
230 for (auto& it : unitsToMonitor)
231 {
232 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
233 "/" + it.first);
234 std::string instanciatedUnitName =
235 std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
236 addInstanceName(
237 std::get<static_cast<int>(monitorElement::instanceName)>(
238 it.second),
239 "@");
240 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
241 server, conn, objPath,
242 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
243 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
244 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
245 it.second),
246 std::get<static_cast<int>(monitorElement::socketObjPath)>(
247 it.second));
248 srvMgrObjects.emplace(
249 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
250 }
251}
252
253void init(sdbusplus::asio::object_server& server,
254 std::shared_ptr<sdbusplus::asio::connection>& conn)
255{
256 // Go through all systemd units, and dynamically detect and manage
257 // the service daemons
258 conn->async_method_call(
259 [&server, &conn](boost::system::error_code ec,
260 const std::vector<ListUnitsType>& listUnits) {
261 if (ec)
262 {
George Liucb267c82022-01-05 17:53:28 +0800263 lg2::error("async_method_call error: ListUnits failed: {EC}",
264 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700265 return;
266 }
267 handleListUnitsResponse(server, conn, ec, listUnits);
268 },
269 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
270}
271
272void checkAndInit(sdbusplus::asio::object_server& server,
273 std::shared_ptr<sdbusplus::asio::connection>& conn)
274{
275 // Check whether systemd completed all the loading before initializing
276 conn->async_method_call(
277 [&server, &conn](boost::system::error_code ec,
278 const std::variant<uint64_t>& value) {
279 if (ec)
280 {
George Liucb267c82022-01-05 17:53:28 +0800281 lg2::error("async_method_call error: ListUnits failed: {EC}",
282 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700283 return;
284 }
285 if (std::get<uint64_t>(value))
286 {
287 if (!unitQueryStarted)
288 {
289 unitQueryStarted = true;
290 init(server, conn);
291 }
292 }
293 else
294 {
295 // FIX-ME: Latest up-stream sync caused issue in receiving
296 // StartupFinished signal. Unable to get StartupFinished signal
297 // from systemd1 hence using poll method too, to trigger it
298 // properly.
299 constexpr size_t pollTimeout = 10; // seconds
300 initTimer->expires_after(std::chrono::seconds(pollTimeout));
301 initTimer->async_wait([&server, &conn](
302 const boost::system::error_code& ec) {
303 if (ec == boost::asio::error::operation_aborted)
304 {
305 // Timer reset.
306 return;
307 }
308 if (ec)
309 {
George Liucb267c82022-01-05 17:53:28 +0800310 lg2::error(
311 "service config mgr - init - async wait error: {EC}",
312 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700313 return;
314 }
315 checkAndInit(server, conn);
316 });
317 }
318 },
319 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
320 "FinishTimestamp");
321}
322
323int main()
324{
325 boost::asio::io_service io;
326 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
327 timer = std::make_unique<boost::asio::steady_timer>(io);
328 initTimer = std::make_unique<boost::asio::steady_timer>(io);
329 conn->request_name(phosphor::service::serviceConfigSrvName);
330 auto server = sdbusplus::asio::object_server(conn, true);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700331 server.add_manager(phosphor::service::srcCfgMgrBasePath);
332 // Initialize the objects after systemd indicated startup finished.
333 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
334 static_cast<sdbusplus::bus::bus&>(*conn),
335 "type='signal',"
336 "member='StartupFinished',path='/org/freedesktop/systemd1',"
337 "interface='org.freedesktop.systemd1.Manager'",
338 [&server, &conn](sdbusplus::message::message& /*msg*/) {
339 if (!unitQueryStarted)
340 {
341 unitQueryStarted = true;
342 init(server, conn);
343 }
344 });
345 // this will make sure to initialize the objects, when daemon is
346 // restarted.
347 checkAndInit(server, conn);
348
349 io.run();
350
351 return 0;
352}