1 /*
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 #include <unordered_map>
27
28 std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
29 std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
30 std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
31 srvMgrObjects;
32 static bool unitQueryStarted = false;
33
34 static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
35 static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
36
37 // Base service name list. All instance of these services and
38 // units(service/socket) will be managed by this daemon.
39 static 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},
44 {"obmc-console-ssh", true}};
45
46 enum class UnitType
47 {
48 service,
49 socket,
50 target,
51 device,
52 invalid
53 };
54
55 using MonitorListMap =
56 std::unordered_map<std::string, std::tuple<std::string, std::string,
57 std::string, std::string>>;
58 MonitorListMap unitsToMonitor;
59
60 enum class monitorElement
61 {
62 unitName,
63 instanceName,
64 serviceObjPath,
65 socketObjPath
66 };
67
68 std::tuple<std::string, UnitType, std::string>
getUnitNameTypeAndInstance(const std::string & fullUnitName)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
handleListUnitsResponse(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn,boost::system::error_code,const std::vector<ListUnitsType> & listUnits)104 static inline void handleListUnitsResponse(
105 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 {
114 // Ignore non-existent units
115 if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
116 loadStateNotFound)
117 {
118 continue;
119 }
120
121 const auto& fullUnitName =
122 std::get<static_cast<int>(ListUnitElements::name)>(unit);
123 auto [unitName, type,
124 instanceName] = getUnitNameTypeAndInstance(fullUnitName);
125 if (managedServices.count(unitName))
126 {
127 // For socket-activated units, ignore all its instances
128 if (managedServices.at(unitName) == true && !instanceName.empty())
129 {
130 continue;
131 }
132
133 std::string instantiatedUnitName =
134 unitName + addInstanceName(instanceName, "@");
135 const sdbusplus::message::object_path& objectPath =
136 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
137 // Group the service & socket units together.. 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 {
145 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
146 value) = objectPath.str;
147 }
148 else if (type == UnitType::socket)
149 {
150 std::get<static_cast<int>(monitorElement::socketObjPath)>(
151 value) = objectPath.str;
152 }
153 continue;
154 }
155 // If not grouped with any existing entry, create a new one
156 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 {
164 unitsToMonitor.emplace(instantiatedUnitName,
165 std::make_tuple(unitName, instanceName,
166 "", objectPath.str));
167 }
168 }
169 }
170
171 bool updateRequired = false;
172 bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
173 if (jsonExist)
174 {
175 try
176 {
177 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)
188 {
189 auto it = savedMonitorList.find(unitIt.first);
190 if (it == savedMonitorList.end())
191 {
192 savedMonitorList.insert(unitIt);
193 updateRequired = true;
194 }
195 }
196 unitsToMonitor = savedMonitorList;
197 }
198 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 }
220 }
221 if (!jsonExist || updateRequired)
222 {
223 std::ofstream file(srvCfgMgrFile);
224 cereal::JSONOutputArchive archive(file);
225 archive(CEREAL_NVP(unitsToMonitor));
226 }
227
228 #ifdef USB_CODE_UPDATE
229 unitsToMonitor.emplace(
230 "phosphor-usb-code-update",
231 std::make_tuple(
232 phosphor::service::usbCodeUpdateUnitName, "",
233 "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
234 ""));
235 #endif
236
237 // create objects for needed services
238 for (auto& it : unitsToMonitor)
239 {
240 sdbusplus::message::object_path basePath(
241 phosphor::service::srcCfgMgrBasePath);
242 std::string objPath(basePath / it.first);
243 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
244 server, conn, objPath,
245 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
246 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
247 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
248 it.second),
249 std::get<static_cast<int>(monitorElement::socketObjPath)>(
250 it.second));
251 srvMgrObjects.emplace(
252 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
253 }
254 }
255
init(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn)256 void init(sdbusplus::asio::object_server& server,
257 std::shared_ptr<sdbusplus::asio::connection>& conn)
258 {
259 // Go through all systemd units, and dynamically detect and manage
260 // the service daemons
261 conn->async_method_call(
262 [&server, &conn](boost::system::error_code ec,
263 const std::vector<ListUnitsType>& listUnits) {
264 if (ec)
265 {
266 lg2::error("async_method_call error: ListUnits failed: {EC}",
267 "EC", ec.value());
268 return;
269 }
270 handleListUnitsResponse(server, conn, ec, listUnits);
271 },
272 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
273 }
274
checkAndInit(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn)275 void checkAndInit(sdbusplus::asio::object_server& server,
276 std::shared_ptr<sdbusplus::asio::connection>& conn)
277 {
278 // Check whether systemd completed all the loading before initializing
279 conn->async_method_call(
280 [&server, &conn](boost::system::error_code ec,
281 const std::variant<uint64_t>& value) {
282 if (ec)
283 {
284 lg2::error("async_method_call error: ListUnits failed: {EC}",
285 "EC", ec.value());
286 return;
287 }
288 if (std::get<uint64_t>(value))
289 {
290 if (!unitQueryStarted)
291 {
292 unitQueryStarted = true;
293 init(server, conn);
294 }
295 }
296 else
297 {
298 // FIX-ME: Latest up-stream sync caused issue in receiving
299 // StartupFinished signal. Unable to get StartupFinished signal
300 // from systemd1 hence using poll method too, to trigger it
301 // properly.
302 constexpr size_t pollTimeout = 10; // seconds
303 initTimer->expires_after(std::chrono::seconds(pollTimeout));
304 initTimer->async_wait([&server, &conn](
305 const boost::system::error_code& ec) {
306 if (ec == boost::asio::error::operation_aborted)
307 {
308 // Timer reset.
309 return;
310 }
311 if (ec)
312 {
313 lg2::error(
314 "service config mgr - init - async wait error: {EC}",
315 "EC", ec.value());
316 return;
317 }
318 checkAndInit(server, conn);
319 });
320 }
321 },
322 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
323 "FinishTimestamp");
324 }
325
main()326 int main()
327 {
328 boost::asio::io_context io;
329 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
330 timer = std::make_unique<boost::asio::steady_timer>(io);
331 initTimer = std::make_unique<boost::asio::steady_timer>(io);
332 conn->request_name(phosphor::service::serviceConfigSrvName);
333 auto server = sdbusplus::asio::object_server(conn, true);
334 server.add_manager(phosphor::service::srcCfgMgrBasePath);
335 // Initialize the objects after systemd indicated startup finished.
336 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>(
337 static_cast<sdbusplus::bus_t&>(*conn),
338 "type='signal',"
339 "member='StartupFinished',path='/org/freedesktop/systemd1',"
340 "interface='org.freedesktop.systemd1.Manager'",
341 [&server, &conn](sdbusplus::message_t& /*msg*/) {
342 if (!unitQueryStarted)
343 {
344 unitQueryStarted = true;
345 init(server, conn);
346 }
347 });
348 // this will make sure to initialize the objects, when daemon is
349 // restarted.
350 checkAndInit(server, conn);
351
352 io.run();
353
354 return 0;
355 }
356