1 /*
2 // Copyright (c) 2017 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
17 #include "Utils.hpp"
18
19 #include "dbus-sensor_config.h"
20
21 #include "DeviceMgmt.hpp"
22 #include "VariantVisitors.hpp"
23
24 #include <boost/asio/error.hpp>
25 #include <boost/asio/steady_timer.hpp>
26 #include <boost/container/flat_map.hpp>
27 #include <phosphor-logging/lg2.hpp>
28 #include <sdbusplus/asio/connection.hpp>
29 #include <sdbusplus/asio/object_server.hpp>
30 #include <sdbusplus/bus.hpp>
31 #include <sdbusplus/bus/match.hpp>
32 #include <sdbusplus/exception.hpp>
33 #include <sdbusplus/message.hpp>
34 #include <sdbusplus/message/native_types.hpp>
35
36 #include <algorithm>
37 #include <array>
38 #include <chrono>
39 #include <cstddef>
40 #include <cstring>
41 #include <filesystem>
42 #include <fstream>
43 #include <functional>
44 #include <iterator>
45 #include <memory>
46 #include <optional>
47 #include <regex>
48 #include <set>
49 #include <span>
50 #include <stdexcept>
51 #include <string>
52 #include <string_view>
53 #include <system_error>
54 #include <tuple>
55 #include <utility>
56 #include <variant>
57 #include <vector>
58
59 static bool powerStatusOn = false;
60 static bool biosHasPost = false;
61 static bool manufacturingMode = false;
62 static bool chassisStatusOn = false;
63
64 static std::unique_ptr<sdbusplus::bus::match_t> powerMatch = nullptr;
65 static std::unique_ptr<sdbusplus::bus::match_t> postMatch = nullptr;
66 static std::unique_ptr<sdbusplus::bus::match_t> chassisMatch = nullptr;
67
68 /**
69 * return the contents of a file
70 * @param[in] hwmonFile - the path to the file to read
71 * @return the contents of the file as a string or nullopt if the file could not
72 * be opened.
73 */
74
openAndRead(const std::string & hwmonFile)75 std::optional<std::string> openAndRead(const std::string& hwmonFile)
76 {
77 std::string fileVal;
78 std::ifstream fileStream(hwmonFile);
79 if (!fileStream.is_open())
80 {
81 return std::nullopt;
82 }
83 std::getline(fileStream, fileVal);
84 return fileVal;
85 }
86
87 /**
88 * given a hwmon temperature base name if valid return the full path else
89 * nullopt
90 * @param[in] directory - the hwmon sysfs directory
91 * @param[in] permitSet - a set of labels or hwmon basenames to permit. If this
92 * is empty then *everything* is permitted.
93 * @return a string to the full path of the file to create a temp sensor with or
94 * nullopt to indicate that no sensor should be created for this basename.
95 */
getFullHwmonFilePath(const std::string & directory,const std::string & hwmonBaseName,const std::set<std::string> & permitSet)96 std::optional<std::string> getFullHwmonFilePath(
97 const std::string& directory, const std::string& hwmonBaseName,
98 const std::set<std::string>& permitSet)
99 {
100 std::optional<std::string> result;
101 std::string filename;
102 if (permitSet.empty())
103 {
104 result = directory + "/" + hwmonBaseName + "_input";
105 return result;
106 }
107 filename = directory + "/" + hwmonBaseName + "_label";
108 auto searchVal = openAndRead(filename);
109 if (!searchVal)
110 {
111 /* if the hwmon temp doesn't have a corresponding label file
112 * then use the hwmon temperature base name
113 */
114 searchVal = hwmonBaseName;
115 }
116 if (permitSet.find(*searchVal) != permitSet.end())
117 {
118 result = directory + "/" + hwmonBaseName + "_input";
119 }
120 return result;
121 }
122
123 /**
124 * retrieve a set of basenames and labels to allow sensor creation for.
125 * @param[in] config - a map representing the configuration for a specific
126 * device
127 * @return a set of basenames and labels to allow sensor creation for. An empty
128 * set indicates that everything is permitted.
129 */
getPermitSet(const SensorBaseConfigMap & config)130 std::set<std::string> getPermitSet(const SensorBaseConfigMap& config)
131 {
132 auto permitAttribute = config.find("Labels");
133 std::set<std::string> permitSet;
134 if (permitAttribute != config.end())
135 {
136 try
137 {
138 auto val =
139 std::get<std::vector<std::string>>(permitAttribute->second);
140
141 permitSet.insert(std::make_move_iterator(val.begin()),
142 std::make_move_iterator(val.end()));
143 }
144 catch (const std::bad_variant_access& err)
145 {
146 lg2::error(
147 "'{ERROR_MESSAGE}': PermitList does not contain a list, wrong variant type.",
148 "ERROR_MESSAGE", err.what());
149 }
150 }
151 return permitSet;
152 }
153
getSensorConfiguration(const std::string & type,const std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,ManagedObjectType & resp,bool useCache)154 bool getSensorConfiguration(
155 const std::string& type,
156 const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
157 ManagedObjectType& resp, bool useCache)
158 {
159 static ManagedObjectType managedObj;
160 std::string typeIntf = configInterfaceName(type);
161
162 if (!useCache)
163 {
164 managedObj.clear();
165 sdbusplus::message_t getManagedObjects =
166 dbusConnection->new_method_call(
167 entityManagerName, "/xyz/openbmc_project/inventory",
168 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
169 try
170 {
171 sdbusplus::message_t reply =
172 dbusConnection->call(getManagedObjects);
173 reply.read(managedObj);
174 }
175 catch (const sdbusplus::exception_t& e)
176 {
177 lg2::error(
178 "While calling GetManagedObjects on service: '{SERVICE_NAME}'"
179 " exception name: '{EXCEPTION_NAME}' and description: "
180 "'{EXCEPTION_DESCRIPTION}' was thrown",
181 "SERVICE_NAME", entityManagerName, "EXCEPTION_NAME", e.name(),
182 "EXCEPTION_DESCRIPTION", e.description());
183 return false;
184 }
185 }
186 for (const auto& pathPair : managedObj)
187 {
188 for (const auto& [intf, cfg] : pathPair.second)
189 {
190 if (intf.starts_with(typeIntf))
191 {
192 resp.emplace(pathPair);
193 break;
194 }
195 }
196 }
197 return true;
198 }
199
findFiles(const std::filesystem::path & dirPath,std::string_view matchString,std::vector<std::filesystem::path> & foundPaths,int symlinkDepth)200 bool findFiles(const std::filesystem::path& dirPath,
201 std::string_view matchString,
202 std::vector<std::filesystem::path>& foundPaths, int symlinkDepth)
203 {
204 std::error_code ec;
205 if (!std::filesystem::exists(dirPath, ec))
206 {
207 return false;
208 }
209
210 std::vector<std::regex> matchPieces;
211
212 size_t pos = 0;
213 std::string token;
214 // Generate the regex expressions list from the match we were given
215 while ((pos = matchString.find('/')) != std::string::npos)
216 {
217 token = matchString.substr(0, pos);
218 matchPieces.emplace_back(token);
219 matchString.remove_prefix(pos + 1);
220 }
221 matchPieces.emplace_back(std::string{matchString});
222
223 // Check if the match string contains directories, and skip the match of
224 // subdirectory if not
225 if (matchPieces.size() <= 1)
226 {
227 std::regex search(std::string{matchString});
228 std::smatch match;
229 for (auto p = std::filesystem::recursive_directory_iterator(
230 dirPath,
231 std::filesystem::directory_options::follow_directory_symlink);
232 p != std::filesystem::recursive_directory_iterator(); ++p)
233 {
234 std::string path = p->path().string();
235 if (!is_directory(*p))
236 {
237 if (std::regex_search(path, match, search))
238 {
239 foundPaths.emplace_back(p->path());
240 }
241 }
242 if (p.depth() >= symlinkDepth)
243 {
244 p.disable_recursion_pending();
245 }
246 }
247 return true;
248 }
249
250 // The match string contains directories, verify each level of sub
251 // directories
252 for (auto p = std::filesystem::recursive_directory_iterator(
253 dirPath,
254 std::filesystem::directory_options::follow_directory_symlink);
255 p != std::filesystem::recursive_directory_iterator(); ++p)
256 {
257 std::vector<std::regex>::iterator matchPiece = matchPieces.begin();
258 std::filesystem::path::iterator pathIt = p->path().begin();
259 for (const std::filesystem::path& dir : dirPath)
260 {
261 if (dir.empty())
262 {
263 // When the path ends with '/', it gets am empty path
264 // skip such case.
265 break;
266 }
267 pathIt++;
268 }
269
270 while (pathIt != p->path().end())
271 {
272 // Found a path deeper than match.
273 if (matchPiece == matchPieces.end())
274 {
275 p.disable_recursion_pending();
276 break;
277 }
278 std::smatch match;
279 std::string component = pathIt->string();
280 std::regex regexPiece(*matchPiece);
281 if (!std::regex_match(component, match, regexPiece))
282 {
283 // path prefix doesn't match, no need to iterate further
284 p.disable_recursion_pending();
285 break;
286 }
287 matchPiece++;
288 pathIt++;
289 }
290
291 if (!is_directory(*p))
292 {
293 if (matchPiece == matchPieces.end())
294 {
295 foundPaths.emplace_back(p->path());
296 }
297 }
298
299 if (p.depth() >= symlinkDepth)
300 {
301 p.disable_recursion_pending();
302 }
303 }
304 return true;
305 }
306
isPowerOn()307 bool isPowerOn()
308 {
309 if (!powerMatch)
310 {
311 throw std::runtime_error("Power Match Not Created");
312 }
313 return powerStatusOn;
314 }
315
hasBiosPost()316 bool hasBiosPost()
317 {
318 if (!postMatch)
319 {
320 throw std::runtime_error("Post Match Not Created");
321 }
322 return biosHasPost;
323 }
324
isChassisOn()325 bool isChassisOn()
326 {
327 if (!chassisMatch)
328 {
329 throw std::runtime_error("Chassis On Match Not Created");
330 }
331 return chassisStatusOn;
332 }
333
readingStateGood(const PowerState & powerState)334 bool readingStateGood(const PowerState& powerState)
335 {
336 if (powerState == PowerState::on && !isPowerOn())
337 {
338 return false;
339 }
340 if (powerState == PowerState::biosPost && (!hasBiosPost() || !isPowerOn()))
341 {
342 return false;
343 }
344 if (powerState == PowerState::chassisOn && !isChassisOn())
345 {
346 return false;
347 }
348
349 return true;
350 }
351
getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection> & conn,size_t retries=2)352 static void getPowerStatus(
353 const std::shared_ptr<sdbusplus::asio::connection>& conn,
354 size_t retries = 2)
355 {
356 conn->async_method_call(
357 [conn, retries](boost::system::error_code ec,
358 const std::variant<std::string>& state) {
359 if (ec)
360 {
361 if (retries != 0U)
362 {
363 auto timer = std::make_shared<boost::asio::steady_timer>(
364 conn->get_io_context());
365 timer->expires_after(std::chrono::seconds(15));
366 timer->async_wait(
367 [timer, conn, retries](boost::system::error_code) {
368 getPowerStatus(conn, retries - 1);
369 });
370 return;
371 }
372
373 // we commonly come up before power control, we'll capture the
374 // property change later
375 lg2::error("error getting power status: '{ERROR_MESSAGE}'",
376 "ERROR_MESSAGE", ec.message());
377 return;
378 }
379 powerStatusOn = std::get<std::string>(state).ends_with(".Running");
380 },
381 power::busname, power::path, properties::interface, properties::get,
382 power::interface, power::property);
383 }
384
getPostStatus(const std::shared_ptr<sdbusplus::asio::connection> & conn,size_t retries=2)385 static void getPostStatus(
386 const std::shared_ptr<sdbusplus::asio::connection>& conn,
387 size_t retries = 2)
388 {
389 conn->async_method_call(
390 [conn, retries](boost::system::error_code ec,
391 const std::variant<std::string>& state) {
392 if (ec)
393 {
394 if (retries != 0U)
395 {
396 auto timer = std::make_shared<boost::asio::steady_timer>(
397 conn->get_io_context());
398 timer->expires_after(std::chrono::seconds(15));
399 timer->async_wait(
400 [timer, conn, retries](boost::system::error_code) {
401 getPostStatus(conn, retries - 1);
402 });
403 return;
404 }
405 // we commonly come up before power control, we'll capture the
406 // property change later
407 lg2::error("error getting post status: '{ERROR_MESSAGE}'",
408 "ERROR_MESSAGE", ec.message());
409 return;
410 }
411 const auto& value = std::get<std::string>(state);
412 biosHasPost = (value != "Inactive") &&
413 (value != "xyz.openbmc_project.State.OperatingSystem."
414 "Status.OSStatus.Inactive");
415 },
416 post::busname, post::path, properties::interface, properties::get,
417 post::interface, post::property);
418 }
419
getChassisStatus(const std::shared_ptr<sdbusplus::asio::connection> & conn,size_t retries=2)420 static void getChassisStatus(
421 const std::shared_ptr<sdbusplus::asio::connection>& conn,
422 size_t retries = 2)
423 {
424 conn->async_method_call(
425 [conn, retries](boost::system::error_code ec,
426 const std::variant<std::string>& state) {
427 if (ec)
428 {
429 if (retries != 0U)
430 {
431 auto timer = std::make_shared<boost::asio::steady_timer>(
432 conn->get_io_context());
433 timer->expires_after(std::chrono::seconds(15));
434 timer->async_wait(
435 [timer, conn, retries](boost::system::error_code) {
436 getChassisStatus(conn, retries - 1);
437 });
438 return;
439 }
440
441 // we commonly come up before power control, we'll capture the
442 // property change later
443 lg2::error(
444 "error getting chassis power status: '{ERROR_MESSAGE}'",
445 "ERROR_MESSAGE", ec.message());
446 return;
447 }
448 chassisStatusOn =
449 std::get<std::string>(state).ends_with(chassis::sOn);
450 },
451 chassis::busname, chassis::path, properties::interface, properties::get,
452 chassis::interface, chassis::property);
453 }
454
setupPowerMatchCallback(const std::shared_ptr<sdbusplus::asio::connection> & conn,std::function<void (PowerState type,bool state)> && hostStatusCallback)455 void setupPowerMatchCallback(
456 const std::shared_ptr<sdbusplus::asio::connection>& conn,
457 std::function<void(PowerState type, bool state)>&& hostStatusCallback)
458 {
459 static boost::asio::steady_timer timer(conn->get_io_context());
460 static boost::asio::steady_timer timerChassisOn(conn->get_io_context());
461 // create a match for powergood changes, first time do a method call to
462 // cache the correct value
463 if (powerMatch)
464 {
465 return;
466 }
467
468 powerMatch = std::make_unique<sdbusplus::bus::match_t>(
469 static_cast<sdbusplus::bus_t&>(*conn),
470 "type='signal',interface='" + std::string(properties::interface) +
471 "',path='" + std::string(power::path) + "',arg0='" +
472 std::string(power::interface) + "'",
473 [hostStatusCallback](sdbusplus::message_t& message) {
474 std::string objectName;
475 boost::container::flat_map<std::string, std::variant<std::string>>
476 values;
477 message.read(objectName, values);
478 auto findState = values.find(power::property);
479 if (findState != values.end())
480 {
481 bool on = std::get<std::string>(findState->second)
482 .ends_with(".Running");
483 if (!on)
484 {
485 timer.cancel();
486 powerStatusOn = false;
487 hostStatusCallback(PowerState::on, powerStatusOn);
488 return;
489 }
490 // on comes too quickly
491 timer.expires_after(std::chrono::seconds(10));
492 timer.async_wait(
493 [hostStatusCallback](boost::system::error_code ec) {
494 if (ec == boost::asio::error::operation_aborted)
495 {
496 return;
497 }
498 if (ec)
499 {
500 lg2::error("Timer error: '{ERROR_MESSAGE}'",
501 "ERROR_MESSAGE", ec.message());
502 return;
503 }
504 powerStatusOn = true;
505 hostStatusCallback(PowerState::on, powerStatusOn);
506 });
507 }
508 });
509
510 postMatch = std::make_unique<sdbusplus::bus::match_t>(
511 static_cast<sdbusplus::bus_t&>(*conn),
512 "type='signal',interface='" + std::string(properties::interface) +
513 "',path='" + std::string(post::path) + "',arg0='" +
514 std::string(post::interface) + "'",
515 [hostStatusCallback](sdbusplus::message_t& message) {
516 std::string objectName;
517 boost::container::flat_map<std::string, std::variant<std::string>>
518 values;
519 message.read(objectName, values);
520 auto findState = values.find(post::property);
521 if (findState != values.end())
522 {
523 auto& value = std::get<std::string>(findState->second);
524 biosHasPost =
525 (value != "Inactive") &&
526 (value != "xyz.openbmc_project.State.OperatingSystem."
527 "Status.OSStatus.Inactive");
528 hostStatusCallback(PowerState::biosPost, biosHasPost);
529 }
530 });
531
532 chassisMatch = std::make_unique<sdbusplus::bus::match_t>(
533 static_cast<sdbusplus::bus_t&>(*conn),
534 "type='signal',interface='" + std::string(properties::interface) +
535 "',path='" + std::string(chassis::path) + "',arg0='" +
536 std::string(chassis::interface) + "'",
537 [hostStatusCallback = std::move(hostStatusCallback)](
538 sdbusplus::message_t& message) {
539 std::string objectName;
540 boost::container::flat_map<std::string, std::variant<std::string>>
541 values;
542 message.read(objectName, values);
543 auto findState = values.find(chassis::property);
544 if (findState != values.end())
545 {
546 bool on = std::get<std::string>(findState->second)
547 .ends_with(chassis::sOn);
548 if (!on)
549 {
550 timerChassisOn.cancel();
551 chassisStatusOn = false;
552 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
553 return;
554 }
555 // on comes too quickly
556 timerChassisOn.expires_after(std::chrono::seconds(10));
557 timerChassisOn.async_wait([hostStatusCallback](
558 boost::system::error_code ec) {
559 if (ec == boost::asio::error::operation_aborted)
560 {
561 return;
562 }
563 if (ec)
564 {
565 lg2::error("Timer error: '{ERROR_MESSAGE}'",
566 "ERROR_MESSAGE", ec.message());
567 return;
568 }
569 chassisStatusOn = true;
570 hostStatusCallback(PowerState::chassisOn, chassisStatusOn);
571 });
572 }
573 });
574 getPowerStatus(conn);
575 getPostStatus(conn);
576 getChassisStatus(conn);
577 }
578
setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection> & conn)579 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
580 {
581 setupPowerMatchCallback(conn, [](PowerState, bool) {});
582 }
583
584 // replaces limits if MinReading and MaxReading are found.
findLimits(std::pair<double,double> & limits,const SensorBaseConfiguration * data)585 void findLimits(std::pair<double, double>& limits,
586 const SensorBaseConfiguration* data)
587 {
588 if (data == nullptr)
589 {
590 return;
591 }
592 auto maxFind = data->second.find("MaxReading");
593 auto minFind = data->second.find("MinReading");
594
595 if (minFind != data->second.end())
596 {
597 limits.first = std::visit(VariantToDoubleVisitor(), minFind->second);
598 }
599 if (maxFind != data->second.end())
600 {
601 limits.second = std::visit(VariantToDoubleVisitor(), maxFind->second);
602 }
603 }
604
createAssociation(std::shared_ptr<sdbusplus::asio::dbus_interface> & association,const std::string & path)605 void createAssociation(
606 std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
607 const std::string& path)
608 {
609 if (association)
610 {
611 std::filesystem::path p(path);
612
613 std::vector<Association> associations;
614 associations.emplace_back("chassis", "all_sensors",
615 p.parent_path().string());
616 association->register_property("Associations", associations);
617 association->initialize();
618 }
619 }
620
setInventoryAssociation(const std::shared_ptr<sdbusplus::asio::dbus_interface> & association,const std::string & inventoryPath,const std::string & chassisPath)621 void setInventoryAssociation(
622 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
623 const std::string& inventoryPath, const std::string& chassisPath)
624 {
625 if (association)
626 {
627 std::vector<Association> associations;
628 associations.emplace_back("inventory", "sensors", inventoryPath);
629 associations.emplace_back("chassis", "all_sensors", chassisPath);
630
631 association->register_property("Associations", associations);
632 association->initialize();
633 }
634 }
635
findContainingChassis(std::string_view configParent,const GetSubTreeType & subtree)636 std::optional<std::string> findContainingChassis(std::string_view configParent,
637 const GetSubTreeType& subtree)
638 {
639 // A parent that is a chassis takes precedence
640 for (const auto& [obj, services] : subtree)
641 {
642 if (obj == configParent)
643 {
644 return obj;
645 }
646 }
647
648 // If the parent is not a chassis, the system chassis is used. This does not
649 // work if there is more than one System, but we assume there is only one
650 // today.
651 for (const auto& [obj, services] : subtree)
652 {
653 for (const auto& [service, interfaces] : services)
654 {
655 if (std::find(interfaces.begin(), interfaces.end(),
656 "xyz.openbmc_project.Inventory.Item.System") !=
657 interfaces.end())
658 {
659 return obj;
660 }
661 }
662 }
663 return std::nullopt;
664 }
665
createInventoryAssoc(const std::shared_ptr<sdbusplus::asio::connection> & conn,const std::shared_ptr<sdbusplus::asio::dbus_interface> & association,const std::string & path)666 void createInventoryAssoc(
667 const std::shared_ptr<sdbusplus::asio::connection>& conn,
668 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
669 const std::string& path)
670 {
671 if (!association)
672 {
673 return;
674 }
675
676 constexpr auto allInterfaces = std::to_array({
677 "xyz.openbmc_project.Inventory.Item.Board",
678 "xyz.openbmc_project.Inventory.Item.Chassis",
679 });
680
681 conn->async_method_call(
682 [association, path](const boost::system::error_code ec,
683 const GetSubTreeType& subtree) {
684 // The parent of the config is always the inventory object, and may
685 // be the associated chassis. If the parent is not itself a chassis
686 // or board, the sensor is associated with the system chassis.
687 std::string parent =
688 std::filesystem::path(path).parent_path().string();
689 if (ec)
690 {
691 // In case of error, set the default associations and
692 // initialize the association Interface.
693 setInventoryAssociation(association, parent, parent);
694 return;
695 }
696 setInventoryAssociation(
697 association, parent,
698 findContainingChassis(parent, subtree).value_or(parent));
699 },
700 mapper::busName, mapper::path, mapper::interface, "GetSubTree",
701 "/xyz/openbmc_project/inventory/system", 2, allInterfaces);
702 }
703
readFile(const std::string & thresholdFile,const double & scaleFactor)704 std::optional<double> readFile(const std::string& thresholdFile,
705 const double& scaleFactor)
706 {
707 std::string line;
708 std::ifstream labelFile(thresholdFile);
709 if (labelFile.good())
710 {
711 std::getline(labelFile, line);
712 labelFile.close();
713
714 try
715 {
716 return std::stod(line) / scaleFactor;
717 }
718 catch (const std::invalid_argument&)
719 {
720 return std::nullopt;
721 }
722 }
723 return std::nullopt;
724 }
725
splitFileName(const std::filesystem::path & filePath)726 std::optional<std::tuple<std::string, std::string, std::string>> splitFileName(
727 const std::filesystem::path& filePath)
728 {
729 if (filePath.has_filename())
730 {
731 const auto fileName = filePath.filename().string();
732
733 size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
734 size_t itemPos = std::strcspn(fileName.c_str(), "_");
735
736 if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
737 {
738 return std::make_optional(
739 std::make_tuple(fileName.substr(0, numberPos),
740 fileName.substr(numberPos, itemPos - numberPos),
741 fileName.substr(itemPos + 1, fileName.size())));
742 }
743 }
744 return std::nullopt;
745 }
746
handleSpecialModeChange(const std::string & manufacturingModeStatus)747 static void handleSpecialModeChange(const std::string& manufacturingModeStatus)
748 {
749 manufacturingMode = false;
750 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
751 "SpecialMode.Modes.Manufacturing")
752 {
753 manufacturingMode = true;
754 }
755 if (validateUnsecureFeature == 1)
756 {
757 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
758 "SpecialMode.Modes.ValidationUnsecure")
759 {
760 manufacturingMode = true;
761 }
762 }
763 }
764
setupManufacturingModeMatch(sdbusplus::asio::connection & conn)765 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn)
766 {
767 namespace rules = sdbusplus::bus::match::rules;
768 static constexpr const char* specialModeInterface =
769 "xyz.openbmc_project.Security.SpecialMode";
770
771 const std::string filterSpecialModeIntfAdd =
772 rules::interfacesAdded() +
773 rules::argNpath(0, "/xyz/openbmc_project/security/special_mode");
774 static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch =
775 std::make_unique<sdbusplus::bus::match_t>(
776 conn, filterSpecialModeIntfAdd, [](sdbusplus::message_t& m) {
777 sdbusplus::message::object_path path;
778 using PropertyMap =
779 boost::container::flat_map<std::string,
780 std::variant<std::string>>;
781 boost::container::flat_map<std::string, PropertyMap>
782 interfaceAdded;
783 m.read(path, interfaceAdded);
784 auto intfItr = interfaceAdded.find(specialModeInterface);
785 if (intfItr == interfaceAdded.end())
786 {
787 return;
788 }
789 PropertyMap& propertyList = intfItr->second;
790 auto itr = propertyList.find("SpecialMode");
791 if (itr == propertyList.end())
792 {
793 lg2::error("error getting SpecialMode property");
794 return;
795 }
796 auto* manufacturingModeStatus =
797 std::get_if<std::string>(&itr->second);
798 handleSpecialModeChange(*manufacturingModeStatus);
799 });
800
801 const std::string filterSpecialModeChange =
802 rules::type::signal() + rules::member("PropertiesChanged") +
803 rules::interface("org.freedesktop.DBus.Properties") +
804 rules::argN(0, specialModeInterface);
805 static std::unique_ptr<sdbusplus::bus::match_t> specialModeChangeMatch =
806 std::make_unique<sdbusplus::bus::match_t>(
807 conn, filterSpecialModeChange, [](sdbusplus::message_t& m) {
808 std::string interfaceName;
809 boost::container::flat_map<std::string,
810 std::variant<std::string>>
811 propertiesChanged;
812
813 m.read(interfaceName, propertiesChanged);
814 auto itr = propertiesChanged.find("SpecialMode");
815 if (itr == propertiesChanged.end())
816 {
817 return;
818 }
819 auto* manufacturingModeStatus =
820 std::get_if<std::string>(&itr->second);
821 handleSpecialModeChange(*manufacturingModeStatus);
822 });
823
824 conn.async_method_call(
825 [](const boost::system::error_code ec,
826 const std::variant<std::string>& getManufactMode) {
827 if (ec)
828 {
829 lg2::error(
830 "error getting SpecialMode status: '{ERROR_MESSAGE}'",
831 "ERROR_MESSAGE", ec.message());
832 return;
833 }
834 const auto* manufacturingModeStatus =
835 std::get_if<std::string>(&getManufactMode);
836 handleSpecialModeChange(*manufacturingModeStatus);
837 },
838 "xyz.openbmc_project.SpecialMode",
839 "/xyz/openbmc_project/security/special_mode",
840 "org.freedesktop.DBus.Properties", "Get", specialModeInterface,
841 "SpecialMode");
842 }
843
getManufacturingMode()844 bool getManufacturingMode()
845 {
846 return manufacturingMode;
847 }
848
849 std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
setupPropertiesChangedMatches(sdbusplus::asio::connection & bus,std::span<const char * const> types,const std::function<void (sdbusplus::message_t &)> & handler)850 setupPropertiesChangedMatches(
851 sdbusplus::asio::connection& bus, std::span<const char* const> types,
852 const std::function<void(sdbusplus::message_t&)>& handler)
853 {
854 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches;
855 for (const char* type : types)
856 {
857 auto match = std::make_unique<sdbusplus::bus::match_t>(
858 static_cast<sdbusplus::bus_t&>(bus),
859 "type='signal',member='PropertiesChanged',path_namespace='" +
860 std::string(inventoryPath) + "',arg0namespace='" +
861 configInterfaceName(type) + "'",
862 handler);
863 matches.emplace_back(std::move(match));
864 }
865 return matches;
866 }
867
868 std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
setupPropertiesChangedMatches(sdbusplus::asio::connection & bus,const I2CDeviceTypeMap & typeMap,const std::function<void (sdbusplus::message_t &)> & handler)869 setupPropertiesChangedMatches(
870 sdbusplus::asio::connection& bus, const I2CDeviceTypeMap& typeMap,
871 const std::function<void(sdbusplus::message_t&)>& handler)
872 {
873 std::vector<const char*> types;
874 types.reserve(typeMap.size());
875 for (const auto& [type, dt] : typeMap)
876 {
877 types.push_back(type.data());
878 }
879 return setupPropertiesChangedMatches(bus, {types}, handler);
880 }
881