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* srvCfgMgrFileOld = "/etc/srvcfg-mgr.json";
35 static constexpr const char* srvCfgMgrFile = "srvcfg-mgr.json";
36 static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
37
38 // Base service name list. All instance of these services and
39 // units(service/socket) will be managed by this daemon.
40 static std::unordered_map<std::string /* unitName */,
41 bool /* isSocketActivated */>
42 managedServices = {{"phosphor-ipmi-net", false}, {"bmcweb", false},
43 {"phosphor-ipmi-kcs", false}, {"obmc-ikvm", false},
44 {"obmc-console", false}, {"dropbear", true},
45 {"obmc-console-ssh", true}, {"ssifbridge", false}};
46
47 enum class UnitType
48 {
49 service,
50 socket,
51 target,
52 device,
53 invalid
54 };
55
56 using MonitorListMap =
57 std::unordered_map<std::string, std::tuple<std::string, std::string,
58 std::string, std::string>>;
59 MonitorListMap unitsToMonitor;
60
61 enum class monitorElement
62 {
63 unitName,
64 instanceName,
65 serviceObjPath,
66 socketObjPath
67 };
68
getUnitNameTypeAndInstance(const std::string & fullUnitName)69 std::tuple<std::string, UnitType, std::string> getUnitNameTypeAndInstance(
70 const std::string& fullUnitName)
71 {
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 {
93 instanceName =
94 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
95 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
handleListUnitsResponse(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn,boost::system::error_code,const std::vector<ListUnitsType> & listUnits)105 static 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)
110 {
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 {
115 // Ignore non-existent units
116 if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
117 loadStateNotFound)
118 {
119 continue;
120 }
121
122 const auto& fullUnitName =
123 std::get<static_cast<int>(ListUnitElements::name)>(unit);
124 auto [unitName, type,
125 instanceName] = getUnitNameTypeAndInstance(fullUnitName);
126 if (managedServices.count(unitName))
127 {
128 // For socket-activated units, ignore all its instances
129 if (managedServices.at(unitName) == true && !instanceName.empty())
130 {
131 continue;
132 }
133
134 std::string instantiatedUnitName =
135 unitName + addInstanceName(instanceName, "@");
136 const sdbusplus::message::object_path& objectPath =
137 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
138 // Group the service & socket units together.. 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 {
146 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
147 value) = objectPath.str;
148 }
149 else if (type == UnitType::socket)
150 {
151 std::get<static_cast<int>(monitorElement::socketObjPath)>(
152 value) = objectPath.str;
153 }
154 continue;
155 }
156 // If not grouped with any existing entry, create a new one
157 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 {
165 unitsToMonitor.emplace(instantiatedUnitName,
166 std::make_tuple(unitName, instanceName,
167 "", objectPath.str));
168 }
169 }
170 }
171
172 bool updateRequired = false;
173
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);
190 std::filesystem::rename(srvCfgMgrFileOld, srvCfgMgrFilePath);
191 }
192
193 bool jsonExist = std::filesystem::exists(srvCfgMgrFilePath);
194 if (jsonExist)
195 {
196 try
197 {
198 std::ifstream file(srvCfgMgrFilePath);
199 cereal::JSONInputArchive archive(file);
200 MonitorListMap savedMonitorList;
201 archive(savedMonitorList);
202
203 // compare the unit list read from systemd1 and the save list.
204 MonitorListMap diffMap;
205 std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
206 begin(savedMonitorList), end(savedMonitorList),
207 std::inserter(diffMap, begin(diffMap)));
208 for (auto& unitIt : diffMap)
209 {
210 auto it = savedMonitorList.find(unitIt.first);
211 if (it == savedMonitorList.end())
212 {
213 savedMonitorList.insert(unitIt);
214 updateRequired = true;
215 }
216 }
217 unitsToMonitor = savedMonitorList;
218 }
219 catch (const std::exception& e)
220 {
221 lg2::error(
222 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
223 "FILEPATH", srvCfgMgrFilePath, "ERROR", e);
224
225 // The "bad" files need to be moved to /tmp/ so that we can try to
226 // find out the cause of the file corruption. If we encounter this
227 // failure multiple times, we will only overwrite it to ensure that
228 // we don't accidentally fill up /tmp/.
229 std::error_code ec;
230 std::filesystem::copy_file(
231 srvCfgMgrFilePath, tmpFileBad,
232 std::filesystem::copy_options::overwrite_existing, ec);
233 if (ec)
234 {
235 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
236 "SRCFILE", srvCfgMgrFilePath, "DSTFILE", tmpFileBad);
237 }
238
239 updateRequired = true;
240 }
241 }
242 if (!jsonExist || updateRequired)
243 {
244 std::ofstream file(srvCfgMgrFilePath);
245 cereal::JSONOutputArchive archive(file);
246 archive(CEREAL_NVP(unitsToMonitor));
247 }
248
249 #ifdef USB_CODE_UPDATE
250 unitsToMonitor.emplace(
251 "phosphor-usb-code-update",
252 std::make_tuple(
253 phosphor::service::usbCodeUpdateUnitName, "",
254 "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
255 ""));
256 #endif
257
258 // create objects for needed services
259 for (auto& it : unitsToMonitor)
260 {
261 sdbusplus::message::object_path basePath(
262 phosphor::service::srcCfgMgrBasePath);
263 std::string objPath(basePath / it.first);
264 auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
265 server, conn, objPath,
266 std::get<static_cast<int>(monitorElement::unitName)>(it.second),
267 std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
268 std::get<static_cast<int>(monitorElement::serviceObjPath)>(
269 it.second),
270 std::get<static_cast<int>(monitorElement::socketObjPath)>(
271 it.second));
272 srvMgrObjects.emplace(
273 std::make_pair(std::move(objPath), std::move(srvCfgObj)));
274 }
275 }
276
init(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn)277 void init(sdbusplus::asio::object_server& server,
278 std::shared_ptr<sdbusplus::asio::connection>& conn)
279 {
280 // Go through all systemd units, and dynamically detect and manage
281 // the service daemons
282 conn->async_method_call(
283 [&server, &conn](boost::system::error_code ec,
284 const std::vector<ListUnitsType>& listUnits) {
285 if (ec)
286 {
287 lg2::error("async_method_call error: ListUnits failed: {EC}",
288 "EC", ec.value());
289 return;
290 }
291 handleListUnitsResponse(server, conn, ec, listUnits);
292 },
293 sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
294 }
295
checkAndInit(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn)296 void checkAndInit(sdbusplus::asio::object_server& server,
297 std::shared_ptr<sdbusplus::asio::connection>& conn)
298 {
299 // Check whether systemd completed all the loading before initializing
300 conn->async_method_call(
301 [&server, &conn](boost::system::error_code ec,
302 const std::variant<uint64_t>& value) {
303 if (ec)
304 {
305 lg2::error("async_method_call error: ListUnits failed: {EC}",
306 "EC", ec.value());
307 return;
308 }
309 if (std::get<uint64_t>(value))
310 {
311 if (!unitQueryStarted)
312 {
313 unitQueryStarted = true;
314 init(server, conn);
315 }
316 }
317 else
318 {
319 // FIX-ME: Latest up-stream sync caused issue in receiving
320 // StartupFinished signal. Unable to get StartupFinished signal
321 // from systemd1 hence using poll method too, to trigger it
322 // properly.
323 constexpr size_t pollTimeout = 10; // seconds
324 initTimer->expires_after(std::chrono::seconds(pollTimeout));
325 initTimer->async_wait([&server, &conn](
326 const boost::system::error_code& ec) {
327 if (ec == boost::asio::error::operation_aborted)
328 {
329 // Timer reset.
330 return;
331 }
332 if (ec)
333 {
334 lg2::error(
335 "service config mgr - init - async wait error: {EC}",
336 "EC", ec.value());
337 return;
338 }
339 checkAndInit(server, conn);
340 });
341 }
342 },
343 sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
344 "FinishTimestamp");
345 }
346
main()347 int main()
348 {
349 boost::asio::io_context io;
350 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
351 timer = std::make_unique<boost::asio::steady_timer>(io);
352 initTimer = std::make_unique<boost::asio::steady_timer>(io);
353 conn->request_name(phosphor::service::serviceConfigSrvName);
354 auto server = sdbusplus::asio::object_server(conn, true);
355 server.add_manager(phosphor::service::srcCfgMgrBasePath);
356 // Initialize the objects after systemd indicated startup finished.
357 auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>(
358 static_cast<sdbusplus::bus_t&>(*conn),
359 "type='signal',"
360 "member='StartupFinished',path='/org/freedesktop/systemd1',"
361 "interface='org.freedesktop.systemd1.Manager'",
362 [&server, &conn](sdbusplus::message_t& /*msg*/) {
363 if (!unitQueryStarted)
364 {
365 unitQueryStarted = true;
366 init(server, conn);
367 }
368 });
369 // this will make sure to initialize the objects, when daemon is
370 // restarted.
371 checkAndInit(server, conn);
372
373 io.run();
374
375 return 0;
376 }
377