blob: 4533512168f1884c8a49bd5e75e86b20239f41aa [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>
Jiaqing Zhaof4766832022-02-28 14:28:18 +080026#include <unordered_map>
Vernon Maueryba2c0832020-07-15 10:02:38 -070027
28std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
29std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
30std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
31 srvMgrObjects;
32static bool unitQueryStarted = false;
33
Andrew Geissler67e41862025-04-22 09:50:30 -050034static constexpr const char* srvCfgMgrFileOld = "/etc/srvcfg-mgr.json";
35static constexpr const char* srvCfgMgrFile = "srvcfg-mgr.json";
George Liuf2744892022-01-05 17:54:45 +080036static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
Vernon Maueryba2c0832020-07-15 10:02:38 -070037
38// Base service name list. All instance of these services and
39// units(service/socket) will be managed by this daemon.
Jiaqing Zhaof4766832022-02-28 14:28:18 +080040static std::unordered_map<std::string /* unitName */,
41 bool /* isSocketActivated */>
42 managedServices = {{"phosphor-ipmi-net", false}, {"bmcweb", false},
Mohammed Javith Akthar Maaca0572024-06-27 17:31:02 -040043 {"phosphor-ipmi-kcs", false}, {"obmc-ikvm", false},
Jiaqing Zhaoef5cece2021-12-10 00:44:11 +080044 {"obmc-console", false}, {"dropbear", true},
leonhuang25c93aa2025-03-10 20:41:56 -070045 {"obmc-console-ssh", true}, {"ssifbridge", false}};
Vernon Maueryba2c0832020-07-15 10:02:38 -070046
47enum class UnitType
48{
49 service,
50 socket,
51 target,
52 device,
53 invalid
54};
55
56using MonitorListMap =
57 std::unordered_map<std::string, std::tuple<std::string, std::string,
58 std::string, std::string>>;
59MonitorListMap unitsToMonitor;
60
61enum class monitorElement
62{
63 unitName,
64 instanceName,
65 serviceObjPath,
66 socketObjPath
67};
68
Patrick Williams3c9cecf2025-02-01 08:23:15 -050069std::tuple<std::string, UnitType, std::string> getUnitNameTypeAndInstance(
70 const std::string& fullUnitName)
Vernon Maueryba2c0832020-07-15 10:02:38 -070071{
72 UnitType type = UnitType::invalid;
73 std::string instanceName;
74 std::string unitName;
75 // get service type
76 auto typePos = fullUnitName.rfind(".");
77 if (typePos != std::string::npos)
78 {
79 const auto& typeStr = fullUnitName.substr(typePos + 1);
80 // Ignore types other than service and socket
81 if (typeStr == "service")
82 {
83 type = UnitType::service;
84 }
85 else if (typeStr == "socket")
86 {
87 type = UnitType::socket;
88 }
89 // get instance name if available
90 auto instancePos = fullUnitName.rfind("@");
91 if (instancePos != std::string::npos)
92 {
Patrick Williamsde879722024-08-16 15:21:46 -040093 instanceName =
94 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
Vernon Maueryba2c0832020-07-15 10:02:38 -070095 unitName = fullUnitName.substr(0, instancePos);
96 }
97 else
98 {
99 unitName = fullUnitName.substr(0, typePos);
100 }
101 }
102 return std::make_tuple(unitName, type, instanceName);
103}
104
Patrick Williamsde879722024-08-16 15:21:46 -0400105static inline void handleListUnitsResponse(
106 sdbusplus::asio::object_server& server,
107 std::shared_ptr<sdbusplus::asio::connection>& conn,
108 boost::system::error_code /*ec*/,
109 const std::vector<ListUnitsType>& listUnits)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700110{
111 // Loop through all units, and mark all units, which has to be
112 // managed, irrespective of instance name.
113 for (const auto& unit : listUnits)
114 {
Jiaqing Zhaod2a99a42022-02-25 17:26:30 +0800115 // Ignore non-existent units
116 if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
117 loadStateNotFound)
118 {
119 continue;
120 }
121
Vernon Maueryba2c0832020-07-15 10:02:38 -0700122 const auto& fullUnitName =
123 std::get<static_cast<int>(ListUnitElements::name)>(unit);
Patrick Williamsdfc72702023-05-10 07:51:18 -0500124 auto [unitName, type,
125 instanceName] = getUnitNameTypeAndInstance(fullUnitName);
Jiaqing Zhaof4766832022-02-28 14:28:18 +0800126 if (managedServices.count(unitName))
Vernon Maueryba2c0832020-07-15 10:02:38 -0700127 {
Jiaqing Zhaof4766832022-02-28 14:28:18 +0800128 // For socket-activated units, ignore all its instances
129 if (managedServices.at(unitName) == true && !instanceName.empty())
130 {
131 continue;
132 }
133
Vernon Maueryba2c0832020-07-15 10:02:38 -0700134 std::string instantiatedUnitName =
Jiaqing Zhao4b8637d2022-04-01 21:25:12 +0800135 unitName + addInstanceName(instanceName, "@");
Vernon Maueryba2c0832020-07-15 10:02:38 -0700136 const sdbusplus::message::object_path& objectPath =
137 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
Manojkiran Eda23ecbb62024-06-17 14:28:01 +0530138 // Group the service & socket units together.. Same services
Vernon Maueryba2c0832020-07-15 10:02:38 -0700139 // are managed together.
140 auto it = unitsToMonitor.find(instantiatedUnitName);
141 if (it != unitsToMonitor.end())
142 {
143 auto& value = it->second;
144 if (type == UnitType::service)
145 {
Vernon Maueryba2c0832020-07-15 10:02:38 -0700146 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800147 value) = objectPath.str;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700148 }
149 else if (type == UnitType::socket)
150 {
151 std::get<static_cast<int>(monitorElement::socketObjPath)>(
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800152 value) = objectPath.str;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700153 }
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800154 continue;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700155 }
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800156 // If not grouped with any existing entry, create a new one
Vernon Maueryba2c0832020-07-15 10:02:38 -0700157 if (type == UnitType::service)
158 {
159 unitsToMonitor.emplace(instantiatedUnitName,
160 std::make_tuple(unitName, instanceName,
161 objectPath.str, ""));
162 }
163 else if (type == UnitType::socket)
164 {
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800165 unitsToMonitor.emplace(instantiatedUnitName,
166 std::make_tuple(unitName, instanceName,
167 "", objectPath.str));
Vernon Maueryba2c0832020-07-15 10:02:38 -0700168 }
169 }
170 }
171
172 bool updateRequired = false;
Andrew Geissler67e41862025-04-22 09:50:30 -0500173
174 // Determine if we need to create our persistent config dir
175 if (!std::filesystem::exists(srvDataBaseDir))
176 {
177 std::filesystem::create_directories(srvDataBaseDir);
178 }
179
180 std::string srvCfgMgrFilePath = std::string(srvDataBaseDir) + srvCfgMgrFile;
181
182 // First check if our config manager file is in the old spot.
183 // If it is, then move it to the new spot
184 if ((std::filesystem::exists(srvCfgMgrFileOld)) &&
185 (!std::filesystem::exists(srvCfgMgrFilePath)))
186 {
187 lg2::info("Moving {OLDFILEPATH} to new location, {FILEPATH}",
188 "OLDFILEPATH", srvCfgMgrFileOld, "FILEPATH",
189 srvCfgMgrFilePath);
Andrew Geisslerb35f7fc2025-10-13 14:09:03 -0500190 // Note that the rename() function can run into issues when /etc
191 // is an overlay on /var so use copy/remove instead
192 std::filesystem::copy(srvCfgMgrFileOld, srvCfgMgrFilePath);
193 std::filesystem::remove(srvCfgMgrFileOld);
Andrew Geissler67e41862025-04-22 09:50:30 -0500194 }
195
196 bool jsonExist = std::filesystem::exists(srvCfgMgrFilePath);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700197 if (jsonExist)
198 {
George Liuf2744892022-01-05 17:54:45 +0800199 try
Vernon Maueryba2c0832020-07-15 10:02:38 -0700200 {
Andrew Geissler67e41862025-04-22 09:50:30 -0500201 std::ifstream file(srvCfgMgrFilePath);
George Liuf2744892022-01-05 17:54:45 +0800202 cereal::JSONInputArchive archive(file);
203 MonitorListMap savedMonitorList;
204 archive(savedMonitorList);
205
206 // compare the unit list read from systemd1 and the save list.
207 MonitorListMap diffMap;
208 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
209 begin(savedMonitorList), end(savedMonitorList),
210 std::inserter(diffMap, begin(diffMap)));
211 for (auto& unitIt : diffMap)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700212 {
George Liuf2744892022-01-05 17:54:45 +0800213 auto it = savedMonitorList.find(unitIt.first);
214 if (it == savedMonitorList.end())
215 {
216 savedMonitorList.insert(unitIt);
217 updateRequired = true;
218 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700219 }
George Liuf2744892022-01-05 17:54:45 +0800220 unitsToMonitor = savedMonitorList;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700221 }
George Liuf2744892022-01-05 17:54:45 +0800222 catch (const std::exception& e)
223 {
224 lg2::error(
225 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
Andrew Geissler67e41862025-04-22 09:50:30 -0500226 "FILEPATH", srvCfgMgrFilePath, "ERROR", e);
George Liuf2744892022-01-05 17:54:45 +0800227
228 // The "bad" files need to be moved to /tmp/ so that we can try to
229 // find out the cause of the file corruption. If we encounter this
230 // failure multiple times, we will only overwrite it to ensure that
231 // we don't accidentally fill up /tmp/.
232 std::error_code ec;
233 std::filesystem::copy_file(
Andrew Geissler67e41862025-04-22 09:50:30 -0500234 srvCfgMgrFilePath, tmpFileBad,
George Liuf2744892022-01-05 17:54:45 +0800235 std::filesystem::copy_options::overwrite_existing, ec);
236 if (ec)
237 {
238 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
Andrew Geissler67e41862025-04-22 09:50:30 -0500239 "SRCFILE", srvCfgMgrFilePath, "DSTFILE", tmpFileBad);
George Liuf2744892022-01-05 17:54:45 +0800240 }
241
242 updateRequired = true;
243 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700244 }
245 if (!jsonExist || updateRequired)
246 {
Andrew Geissler67e41862025-04-22 09:50:30 -0500247 std::ofstream file(srvCfgMgrFilePath);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700248 cereal::JSONOutputArchive archive(file);
249 archive(CEREAL_NVP(unitsToMonitor));
250 }
251
Chicago Duan25a0f632021-11-11 16:32:07 +0800252#ifdef USB_CODE_UPDATE
253 unitsToMonitor.emplace(
Jiaqing Zhao4b8637d2022-04-01 21:25:12 +0800254 "phosphor-usb-code-update",
Chicago Duan25a0f632021-11-11 16:32:07 +0800255 std::make_tuple(
Jiaqing Zhao430d7ea2022-04-01 23:23:25 +0800256 phosphor::service::usbCodeUpdateUnitName, "",
Chicago Duan25a0f632021-11-11 16:32:07 +0800257 "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
258 ""));
259#endif
260
Vernon Maueryba2c0832020-07-15 10:02:38 -0700261 // create objects for needed services
262 for (auto& it : unitsToMonitor)
263 {
Jiaqing Zhao4b8637d2022-04-01 21:25:12 +0800264 sdbusplus::message::object_path basePath(
265 phosphor::service::srcCfgMgrBasePath);
266 std::string objPath(basePath / it.first);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700267 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
268 server, conn, objPath,
269 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
270 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
271 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
272 it.second),
273 std::get<static_cast<int>(monitorElement::socketObjPath)>(
274 it.second));
275 srvMgrObjects.emplace(
276 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
277 }
278}
279
280void init(sdbusplus::asio::object_server& server,
281 std::shared_ptr<sdbusplus::asio::connection>& conn)
282{
283 // Go through all systemd units, and dynamically detect and manage
284 // the service daemons
285 conn->async_method_call(
286 [&server, &conn](boost::system::error_code ec,
287 const std::vector<ListUnitsType>& listUnits) {
Patrick Williamsde879722024-08-16 15:21:46 -0400288 if (ec)
289 {
290 lg2::error("async_method_call error: ListUnits failed: {EC}",
291 "EC", ec.value());
292 return;
293 }
294 handleListUnitsResponse(server, conn, ec, listUnits);
295 },
Vernon Maueryba2c0832020-07-15 10:02:38 -0700296 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
297}
298
299void checkAndInit(sdbusplus::asio::object_server& server,
300 std::shared_ptr<sdbusplus::asio::connection>& conn)
301{
302 // Check whether systemd completed all the loading before initializing
303 conn->async_method_call(
304 [&server, &conn](boost::system::error_code ec,
305 const std::variant<uint64_t>& value) {
Patrick Williamsde879722024-08-16 15:21:46 -0400306 if (ec)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700307 {
Patrick Williamsde879722024-08-16 15:21:46 -0400308 lg2::error("async_method_call error: ListUnits failed: {EC}",
309 "EC", ec.value());
310 return;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700311 }
Patrick Williamsde879722024-08-16 15:21:46 -0400312 if (std::get<uint64_t>(value))
313 {
314 if (!unitQueryStarted)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700315 {
Patrick Williamsde879722024-08-16 15:21:46 -0400316 unitQueryStarted = true;
317 init(server, conn);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700318 }
Patrick Williamsde879722024-08-16 15:21:46 -0400319 }
320 else
321 {
322 // FIX-ME: Latest up-stream sync caused issue in receiving
323 // StartupFinished signal. Unable to get StartupFinished signal
324 // from systemd1 hence using poll method too, to trigger it
325 // properly.
326 constexpr size_t pollTimeout = 10; // seconds
327 initTimer->expires_after(std::chrono::seconds(pollTimeout));
328 initTimer->async_wait([&server, &conn](
329 const boost::system::error_code& ec) {
330 if (ec == boost::asio::error::operation_aborted)
331 {
332 // Timer reset.
333 return;
334 }
335 if (ec)
336 {
337 lg2::error(
338 "service config mgr - init - async wait error: {EC}",
339 "EC", ec.value());
340 return;
341 }
342 checkAndInit(server, conn);
343 });
344 }
345 },
Vernon Maueryba2c0832020-07-15 10:02:38 -0700346 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
347 "FinishTimestamp");
348}
349
350int main()
351{
Ed Tanousba287d82023-03-01 10:42:28 -0800352 boost::asio::io_context io;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700353 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
354 timer = std::make_unique<boost::asio::steady_timer>(io);
355 initTimer = std::make_unique<boost::asio::steady_timer>(io);
356 conn->request_name(phosphor::service::serviceConfigSrvName);
357 auto server = sdbusplus::asio::object_server(conn, true);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700358 server.add_manager(phosphor::service::srcCfgMgrBasePath);
359 // Initialize the objects after systemd indicated startup finished.
Patrick Williams2ff37282022-07-22 19:26:57 -0500360 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>(
361 static_cast<sdbusplus::bus_t&>(*conn),
Vernon Maueryba2c0832020-07-15 10:02:38 -0700362 "type='signal',"
363 "member='StartupFinished',path='/org/freedesktop/systemd1',"
364 "interface='org.freedesktop.systemd1.Manager'",
Patrick Williams2ff37282022-07-22 19:26:57 -0500365 [&server, &conn](sdbusplus::message_t& /*msg*/) {
Patrick Williamsde879722024-08-16 15:21:46 -0400366 if (!unitQueryStarted)
367 {
368 unitQueryStarted = true;
369 init(server, conn);
370 }
371 });
Vernon Maueryba2c0832020-07-15 10:02:38 -0700372 // this will make sure to initialize the objects, when daemon is
373 // restarted.
374 checkAndInit(server, conn);
375
376 io.run();
377
378 return 0;
379}