blob: 8e7a8a73a942c065b78cc5d0b786af2d1ca50be5 [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},
43 {"obmc-console", false}, {"dropbear", true}};
Vernon Maueryba2c0832020-07-15 10:02:38 -070044
45enum class UnitType
46{
47 service,
48 socket,
49 target,
50 device,
51 invalid
52};
53
54using MonitorListMap =
55 std::unordered_map<std::string, std::tuple<std::string, std::string,
56 std::string, std::string>>;
57MonitorListMap unitsToMonitor;
58
59enum class monitorElement
60{
61 unitName,
62 instanceName,
63 serviceObjPath,
64 socketObjPath
65};
66
67std::tuple<std::string, UnitType, std::string>
68 getUnitNameTypeAndInstance(const std::string& fullUnitName)
69{
70 UnitType type = UnitType::invalid;
71 std::string instanceName;
72 std::string unitName;
73 // get service type
74 auto typePos = fullUnitName.rfind(".");
75 if (typePos != std::string::npos)
76 {
77 const auto& typeStr = fullUnitName.substr(typePos + 1);
78 // Ignore types other than service and socket
79 if (typeStr == "service")
80 {
81 type = UnitType::service;
82 }
83 else if (typeStr == "socket")
84 {
85 type = UnitType::socket;
86 }
87 // get instance name if available
88 auto instancePos = fullUnitName.rfind("@");
89 if (instancePos != std::string::npos)
90 {
91 instanceName =
92 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
93 unitName = fullUnitName.substr(0, instancePos);
94 }
95 else
96 {
97 unitName = fullUnitName.substr(0, typePos);
98 }
99 }
100 return std::make_tuple(unitName, type, instanceName);
101}
102
103static inline void
104 handleListUnitsResponse(sdbusplus::asio::object_server& server,
105 std::shared_ptr<sdbusplus::asio::connection>& conn,
106 boost::system::error_code /*ec*/,
107 const std::vector<ListUnitsType>& listUnits)
108{
109 // Loop through all units, and mark all units, which has to be
110 // managed, irrespective of instance name.
111 for (const auto& unit : listUnits)
112 {
Jiaqing Zhaod2a99a42022-02-25 17:26:30 +0800113 // Ignore non-existent units
114 if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
115 loadStateNotFound)
116 {
117 continue;
118 }
119
Vernon Maueryba2c0832020-07-15 10:02:38 -0700120 const auto& fullUnitName =
121 std::get<static_cast<int>(ListUnitElements::name)>(unit);
122 auto [unitName, type, instanceName] =
123 getUnitNameTypeAndInstance(fullUnitName);
Jiaqing Zhaof4766832022-02-28 14:28:18 +0800124 if (managedServices.count(unitName))
Vernon Maueryba2c0832020-07-15 10:02:38 -0700125 {
Jiaqing Zhaof4766832022-02-28 14:28:18 +0800126 // For socket-activated units, ignore all its instances
127 if (managedServices.at(unitName) == true && !instanceName.empty())
128 {
129 continue;
130 }
131
Vernon Maueryba2c0832020-07-15 10:02:38 -0700132 std::string instantiatedUnitName =
133 unitName + addInstanceName(instanceName, "_40");
134 boost::replace_all(instantiatedUnitName, "-", "_2d");
135 const sdbusplus::message::object_path& objectPath =
136 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
137 // Group the service & socket units togther.. Same services
138 // are managed together.
139 auto it = unitsToMonitor.find(instantiatedUnitName);
140 if (it != unitsToMonitor.end())
141 {
142 auto& value = it->second;
143 if (type == UnitType::service)
144 {
Vernon Maueryba2c0832020-07-15 10:02:38 -0700145 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800146 value) = objectPath.str;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700147 }
148 else if (type == UnitType::socket)
149 {
150 std::get<static_cast<int>(monitorElement::socketObjPath)>(
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800151 value) = objectPath.str;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700152 }
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800153 continue;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700154 }
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800155 // If not grouped with any existing entry, create a new one
Vernon Maueryba2c0832020-07-15 10:02:38 -0700156 if (type == UnitType::service)
157 {
158 unitsToMonitor.emplace(instantiatedUnitName,
159 std::make_tuple(unitName, instanceName,
160 objectPath.str, ""));
161 }
162 else if (type == UnitType::socket)
163 {
Jiaqing Zhaoaa41e672021-12-14 15:14:30 +0800164 unitsToMonitor.emplace(instantiatedUnitName,
165 std::make_tuple(unitName, instanceName,
166 "", objectPath.str));
Vernon Maueryba2c0832020-07-15 10:02:38 -0700167 }
168 }
169 }
170
171 bool updateRequired = false;
172 bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
173 if (jsonExist)
174 {
George Liuf2744892022-01-05 17:54:45 +0800175 try
Vernon Maueryba2c0832020-07-15 10:02:38 -0700176 {
George Liuf2744892022-01-05 17:54:45 +0800177 std::ifstream file(srvCfgMgrFile);
178 cereal::JSONInputArchive archive(file);
179 MonitorListMap savedMonitorList;
180 archive(savedMonitorList);
181
182 // compare the unit list read from systemd1 and the save list.
183 MonitorListMap diffMap;
184 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
185 begin(savedMonitorList), end(savedMonitorList),
186 std::inserter(diffMap, begin(diffMap)));
187 for (auto& unitIt : diffMap)
Vernon Maueryba2c0832020-07-15 10:02:38 -0700188 {
George Liuf2744892022-01-05 17:54:45 +0800189 auto it = savedMonitorList.find(unitIt.first);
190 if (it == savedMonitorList.end())
191 {
192 savedMonitorList.insert(unitIt);
193 updateRequired = true;
194 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700195 }
George Liuf2744892022-01-05 17:54:45 +0800196 unitsToMonitor = savedMonitorList;
Vernon Maueryba2c0832020-07-15 10:02:38 -0700197 }
George Liuf2744892022-01-05 17:54:45 +0800198 catch (const std::exception& e)
199 {
200 lg2::error(
201 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
202 "FILEPATH", srvCfgMgrFile, "ERROR", e);
203
204 // The "bad" files need to be moved to /tmp/ so that we can try to
205 // find out the cause of the file corruption. If we encounter this
206 // failure multiple times, we will only overwrite it to ensure that
207 // we don't accidentally fill up /tmp/.
208 std::error_code ec;
209 std::filesystem::copy_file(
210 srvCfgMgrFile, tmpFileBad,
211 std::filesystem::copy_options::overwrite_existing, ec);
212 if (ec)
213 {
214 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
215 "SRCFILE", srvCfgMgrFile, "DSTFILE", tmpFileBad);
216 }
217
218 updateRequired = true;
219 }
Vernon Maueryba2c0832020-07-15 10:02:38 -0700220 }
221 if (!jsonExist || updateRequired)
222 {
223 std::ofstream file(srvCfgMgrFile);
224 cereal::JSONOutputArchive archive(file);
225 archive(CEREAL_NVP(unitsToMonitor));
226 }
227
Chicago Duan25a0f632021-11-11 16:32:07 +0800228#ifdef USB_CODE_UPDATE
229 unitsToMonitor.emplace(
230 "phosphor_2dusb_2dcode_2dupdate",
231 std::make_tuple(
232 "phosphor_usb_code_update", "",
233 "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
234 ""));
235#endif
236
Vernon Maueryba2c0832020-07-15 10:02:38 -0700237 // create objects for needed services
238 for (auto& it : unitsToMonitor)
239 {
240 std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
241 "/" + it.first);
242 std::string instanciatedUnitName =
243 std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
244 addInstanceName(
245 std::get<static_cast<int>(monitorElement::instanceName)>(
246 it.second),
247 "@");
248 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
249 server, conn, objPath,
250 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
251 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
252 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
253 it.second),
254 std::get<static_cast<int>(monitorElement::socketObjPath)>(
255 it.second));
256 srvMgrObjects.emplace(
257 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
258 }
259}
260
261void init(sdbusplus::asio::object_server& server,
262 std::shared_ptr<sdbusplus::asio::connection>& conn)
263{
264 // Go through all systemd units, and dynamically detect and manage
265 // the service daemons
266 conn->async_method_call(
267 [&server, &conn](boost::system::error_code ec,
268 const std::vector<ListUnitsType>& listUnits) {
269 if (ec)
270 {
George Liucb267c82022-01-05 17:53:28 +0800271 lg2::error("async_method_call error: ListUnits failed: {EC}",
272 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700273 return;
274 }
275 handleListUnitsResponse(server, conn, ec, listUnits);
276 },
277 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
278}
279
280void checkAndInit(sdbusplus::asio::object_server& server,
281 std::shared_ptr<sdbusplus::asio::connection>& conn)
282{
283 // Check whether systemd completed all the loading before initializing
284 conn->async_method_call(
285 [&server, &conn](boost::system::error_code ec,
286 const std::variant<uint64_t>& value) {
287 if (ec)
288 {
George Liucb267c82022-01-05 17:53:28 +0800289 lg2::error("async_method_call error: ListUnits failed: {EC}",
290 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700291 return;
292 }
293 if (std::get<uint64_t>(value))
294 {
295 if (!unitQueryStarted)
296 {
297 unitQueryStarted = true;
298 init(server, conn);
299 }
300 }
301 else
302 {
303 // FIX-ME: Latest up-stream sync caused issue in receiving
304 // StartupFinished signal. Unable to get StartupFinished signal
305 // from systemd1 hence using poll method too, to trigger it
306 // properly.
307 constexpr size_t pollTimeout = 10; // seconds
308 initTimer->expires_after(std::chrono::seconds(pollTimeout));
309 initTimer->async_wait([&server, &conn](
310 const boost::system::error_code& ec) {
311 if (ec == boost::asio::error::operation_aborted)
312 {
313 // Timer reset.
314 return;
315 }
316 if (ec)
317 {
George Liucb267c82022-01-05 17:53:28 +0800318 lg2::error(
319 "service config mgr - init - async wait error: {EC}",
320 "EC", ec.value());
Vernon Maueryba2c0832020-07-15 10:02:38 -0700321 return;
322 }
323 checkAndInit(server, conn);
324 });
325 }
326 },
327 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
328 "FinishTimestamp");
329}
330
331int main()
332{
333 boost::asio::io_service io;
334 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
335 timer = std::make_unique<boost::asio::steady_timer>(io);
336 initTimer = std::make_unique<boost::asio::steady_timer>(io);
337 conn->request_name(phosphor::service::serviceConfigSrvName);
338 auto server = sdbusplus::asio::object_server(conn, true);
Vernon Maueryba2c0832020-07-15 10:02:38 -0700339 server.add_manager(phosphor::service::srcCfgMgrBasePath);
340 // Initialize the objects after systemd indicated startup finished.
341 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
342 static_cast<sdbusplus::bus::bus&>(*conn),
343 "type='signal',"
344 "member='StartupFinished',path='/org/freedesktop/systemd1',"
345 "interface='org.freedesktop.systemd1.Manager'",
346 [&server, &conn](sdbusplus::message::message& /*msg*/) {
347 if (!unitQueryStarted)
348 {
349 unitQueryStarted = true;
350 init(server, conn);
351 }
352 });
353 // this will make sure to initialize the objects, when daemon is
354 // restarted.
355 checkAndInit(server, conn);
356
357 io.run();
358
359 return 0;
360}