blob: 36f8cbb249df320a9809e49e08fd79814ae70acb [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
34static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
George Liuf2744892022-01-05 17:54:45 +080035static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
Vernon Maueryba2c0832020-07-15 10:02:38 -070036
37// Base service name list. All instance of these services and
38// units(service/socket) will be managed by this daemon.
Jiaqing Zhaof4766832022-02-28 14:28:18 +080039static std::unordered_map<std::string /* unitName */,
40 bool /* isSocketActivated */>
41 managedServices = {{"phosphor-ipmi-net", false}, {"bmcweb", false},
42 {"phosphor-ipmi-kcs", false}, {"start-ipkvm", false},
Jiaqing Zhaoef5cece2021-12-10 00:44:11 +080043 {"obmc-console", false}, {"dropbear", true},
44 {"obmc-console-ssh", true}};
Vernon Maueryba2c0832020-07-15 10:02:38 -070045
46enum class UnitType
47{
48 service,
49 socket,
50 target,
51 device,
52 invalid
53};
54
55using MonitorListMap =
56 std::unordered_map<std::string, std::tuple<std::string, std::string,
57 std::string, std::string>>;
58MonitorListMap unitsToMonitor;
59
60enum class monitorElement
61{
62 unitName,
63 instanceName,
64 serviceObjPath,
65 socketObjPath
66};
67
68std::tuple<std::string, UnitType, std::string>
69 getUnitNameTypeAndInstance(const std::string& fullUnitName)
70{
71 UnitType type = UnitType::invalid;
72 std::string instanceName;
73 std::string unitName;
74 // get service type
75 auto typePos = fullUnitName.rfind(".");
76 if (typePos != std::string::npos)
77 {
78 const auto& typeStr = fullUnitName.substr(typePos + 1);
79 // Ignore types other than service and socket
80 if (typeStr == "service")
81 {
82 type = UnitType::service;
83 }
84 else if (typeStr == "socket")
85 {
86 type = UnitType::socket;
87 }
88 // get instance name if available
89 auto instancePos = fullUnitName.rfind("@");
90 if (instancePos != std::string::npos)
91 {
92 instanceName =
93 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
94 unitName = fullUnitName.substr(0, instancePos);
95 }
96 else
97 {
98 unitName = fullUnitName.substr(0, typePos);
99 }
100 }
101 return std::make_tuple(unitName, type, instanceName);
102}
103
104static inline void
105 handleListUnitsResponse(sdbusplus::asio::object_server& server,
106 std::shared_ptr<sdbusplus::asio::connection>& conn,
107 boost::system::error_code /*ec*/,
108 const std::vector<ListUnitsType>& listUnits)
109{
110 // Loop through all units, and mark all units, which has to be
111 // managed, irrespective of instance name.
112 for (const auto& unit : listUnits)
113 {
Jiaqing Zhaod2a99a42022-02-25 17:26:30 +0800114 // Ignore non-existent units
115 if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
116 loadStateNotFound)
117 {
118 continue;
119 }
120
Vernon Maueryba2c0832020-07-15 10:02:38 -0700121 const auto& fullUnitName =
122 std::get<static_cast<int>(ListUnitElements::name)>(unit);
123 auto [unitName, type, instanceName] =
124 getUnitNameTypeAndInstance(fullUnitName);
Jiaqing Zhaof4766832022-02-28 14:28:18 +0800125 if (managedServices.count(unitName))
Vernon Maueryba2c0832020-07-15 10:02:38 -0700126 {
Jiaqing Zhaof4766832022-02-28 14:28:18 +0800127 // For socket-activated units, ignore all its instances
128 if (managedServices.at(unitName) == true && !instanceName.empty())
129 {
130 continue;
131 }
132
Vernon Maueryba2c0832020-07-15 10:02:38 -0700133 std::string instantiatedUnitName =
134 unitName + addInstanceName(instanceName, "_40");
135 boost::replace_all(instantiatedUnitName, "-", "_2d");
136 const sdbusplus::message::object_path& objectPath =
137 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
138 // Group the service & socket units togther.. Same services
139 // 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;
173 bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
174 if (jsonExist)
175 {
George Liuf2744892022-01-05 17:54:45 +0800176 try
Vernon Maueryba2c0832020-07-15 10:02:38 -0700177 {
George Liuf2744892022-01-05 17:54:45 +0800178 std::ifstream file(srvCfgMgrFile);
179 cereal::JSONInputArchive archive(file);
180 MonitorListMap savedMonitorList;
181 archive(savedMonitorList);
182
183 // compare the unit list read from systemd1 and the save list.
184 MonitorListMap diffMap;
185 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
186 begin(savedMonitorList), end(savedMonitorList),
187 std::inserter(diffMap, begin(diffMap)));
188 for (auto& unitIt : diffMap)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700189 {
George Liuf2744892022-01-05 17:54:45 +0800190 auto it = savedMonitorList.find(unitIt.first);
191 if (it == savedMonitorList.end())
192 {
193 savedMonitorList.insert(unitIt);
194 updateRequired = true;
195 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700196 }
George Liuf2744892022-01-05 17:54:45 +0800197 unitsToMonitor = savedMonitorList;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700198 }
George Liuf2744892022-01-05 17:54:45 +0800199 catch (const std::exception& e)
200 {
201 lg2::error(
202 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
203 "FILEPATH", srvCfgMgrFile, "ERROR", e);
204
205 // The "bad" files need to be moved to /tmp/ so that we can try to
206 // find out the cause of the file corruption. If we encounter this
207 // failure multiple times, we will only overwrite it to ensure that
208 // we don't accidentally fill up /tmp/.
209 std::error_code ec;
210 std::filesystem::copy_file(
211 srvCfgMgrFile, tmpFileBad,
212 std::filesystem::copy_options::overwrite_existing, ec);
213 if (ec)
214 {
215 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
216 "SRCFILE", srvCfgMgrFile, "DSTFILE", tmpFileBad);
217 }
218
219 updateRequired = true;
220 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700221 }
222 if (!jsonExist || updateRequired)
223 {
224 std::ofstream file(srvCfgMgrFile);
225 cereal::JSONOutputArchive archive(file);
226 archive(CEREAL_NVP(unitsToMonitor));
227 }
228
Chicago Duan25a0f632021-11-11 16:32:07 +0800229#ifdef USB_CODE_UPDATE
230 unitsToMonitor.emplace(
231 "phosphor_2dusb_2dcode_2dupdate",
232 std::make_tuple(
Jiaqing Zhao430d7ea2022-04-01 23:23:25 +0800233 phosphor::service::usbCodeUpdateUnitName, "",
Chicago Duan25a0f632021-11-11 16:32:07 +0800234 "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
235 ""));
236#endif
237
Vernon Maueryba2c0832020-07-15 10:02:38 -0700238 // create objects for needed services
239 for (auto& it : unitsToMonitor)
240 {
241 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
242 "/" + it.first);
243 std::string instanciatedUnitName =
244 std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
245 addInstanceName(
246 std::get<static_cast<int>(monitorElement::instanceName)>(
247 it.second),
248 "@");
249 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
250 server, conn, objPath,
251 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
252 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
253 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
254 it.second),
255 std::get<static_cast<int>(monitorElement::socketObjPath)>(
256 it.second));
257 srvMgrObjects.emplace(
258 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
259 }
260}
261
262void init(sdbusplus::asio::object_server& server,
263 std::shared_ptr<sdbusplus::asio::connection>& conn)
264{
265 // Go through all systemd units, and dynamically detect and manage
266 // the service daemons
267 conn->async_method_call(
268 [&server, &conn](boost::system::error_code ec,
269 const std::vector<ListUnitsType>& listUnits) {
270 if (ec)
271 {
George Liucb267c82022-01-05 17:53:28 +0800272 lg2::error("async_method_call error: ListUnits failed: {EC}",
273 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700274 return;
275 }
276 handleListUnitsResponse(server, conn, ec, listUnits);
277 },
278 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
279}
280
281void checkAndInit(sdbusplus::asio::object_server& server,
282 std::shared_ptr<sdbusplus::asio::connection>& conn)
283{
284 // Check whether systemd completed all the loading before initializing
285 conn->async_method_call(
286 [&server, &conn](boost::system::error_code ec,
287 const std::variant<uint64_t>& value) {
288 if (ec)
289 {
George Liucb267c82022-01-05 17:53:28 +0800290 lg2::error("async_method_call error: ListUnits failed: {EC}",
291 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700292 return;
293 }
294 if (std::get<uint64_t>(value))
295 {
296 if (!unitQueryStarted)
297 {
298 unitQueryStarted = true;
299 init(server, conn);
300 }
301 }
302 else
303 {
304 // FIX-ME: Latest up-stream sync caused issue in receiving
305 // StartupFinished signal. Unable to get StartupFinished signal
306 // from systemd1 hence using poll method too, to trigger it
307 // properly.
308 constexpr size_t pollTimeout = 10; // seconds
309 initTimer->expires_after(std::chrono::seconds(pollTimeout));
310 initTimer->async_wait([&server, &conn](
311 const boost::system::error_code& ec) {
312 if (ec == boost::asio::error::operation_aborted)
313 {
314 // Timer reset.
315 return;
316 }
317 if (ec)
318 {
George Liucb267c82022-01-05 17:53:28 +0800319 lg2::error(
320 "service config mgr - init - async wait error: {EC}",
321 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700322 return;
323 }
324 checkAndInit(server, conn);
325 });
326 }
327 },
328 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
329 "FinishTimestamp");
330}
331
332int main()
333{
334 boost::asio::io_service io;
335 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
336 timer = std::make_unique<boost::asio::steady_timer>(io);
337 initTimer = std::make_unique<boost::asio::steady_timer>(io);
338 conn->request_name(phosphor::service::serviceConfigSrvName);
339 auto server = sdbusplus::asio::object_server(conn, true);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700340 server.add_manager(phosphor::service::srcCfgMgrBasePath);
341 // Initialize the objects after systemd indicated startup finished.
342 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
343 static_cast<sdbusplus::bus::bus&>(*conn),
344 "type='signal',"
345 "member='StartupFinished',path='/org/freedesktop/systemd1',"
346 "interface='org.freedesktop.systemd1.Manager'",
347 [&server, &conn](sdbusplus::message::message& /*msg*/) {
348 if (!unitQueryStarted)
349 {
350 unitQueryStarted = true;
351 init(server, conn);
352 }
353 });
354 // this will make sure to initialize the objects, when daemon is
355 // restarted.
356 checkAndInit(server, conn);
357
358 io.run();
359
360 return 0;
361}