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.contains(*searchVal))
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::weak_ptr<sdbusplus::asio::dbus_interface> & weakRef,const std::string & inventoryPath,const std::string & chassisPath)621 void setInventoryAssociation(
622 const std::weak_ptr<sdbusplus::asio::dbus_interface>& weakRef,
623 const std::string& inventoryPath, const std::string& chassisPath)
624 {
625 auto association = weakRef.lock();
626 if (!association)
627 {
628 return;
629 }
630
631 std::vector<Association> associations;
632 associations.emplace_back("inventory", "sensors", inventoryPath);
633 associations.emplace_back("chassis", "all_sensors", chassisPath);
634
635 association->register_property("Associations", associations);
636 association->initialize();
637 }
638
findContainingChassis(std::string_view configParent,const GetSubTreeType & subtree)639 std::optional<std::string> findContainingChassis(std::string_view configParent,
640 const GetSubTreeType& subtree)
641 {
642 // A parent that is a chassis takes precedence
643 for (const auto& [obj, services] : subtree)
644 {
645 if (obj == configParent)
646 {
647 return obj;
648 }
649 }
650
651 // If the parent is not a chassis, the system chassis is used. This does not
652 // work if there is more than one System, but we assume there is only one
653 // today.
654 for (const auto& [obj, services] : subtree)
655 {
656 for (const auto& [service, interfaces] : services)
657 {
658 if (std::find(interfaces.begin(), interfaces.end(),
659 "xyz.openbmc_project.Inventory.Item.System") !=
660 interfaces.end())
661 {
662 return obj;
663 }
664 }
665 }
666 return std::nullopt;
667 }
668
createInventoryAssoc(const std::shared_ptr<sdbusplus::asio::connection> & conn,const std::shared_ptr<sdbusplus::asio::dbus_interface> & association,const std::string & path)669 void createInventoryAssoc(
670 const std::shared_ptr<sdbusplus::asio::connection>& conn,
671 const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
672 const std::string& path)
673 {
674 if (!association)
675 {
676 return;
677 }
678
679 constexpr auto allInterfaces = std::to_array({
680 "xyz.openbmc_project.Inventory.Item.Board",
681 "xyz.openbmc_project.Inventory.Item.Chassis",
682 });
683
684 std::weak_ptr<sdbusplus::asio::dbus_interface> weakRef = association;
685 conn->async_method_call(
686 [weakRef, path](const boost::system::error_code ec,
687 const GetSubTreeType& subtree) {
688 // The parent of the config is always the inventory object, and may
689 // be the associated chassis. If the parent is not itself a chassis
690 // or board, the sensor is associated with the system chassis.
691 std::string parent =
692 std::filesystem::path(path).parent_path().string();
693 if (ec)
694 {
695 // In case of error, set the default associations and
696 // initialize the association Interface.
697 setInventoryAssociation(weakRef, parent, parent);
698 return;
699 }
700 setInventoryAssociation(
701 weakRef, parent,
702 findContainingChassis(parent, subtree).value_or(parent));
703 },
704 mapper::busName, mapper::path, mapper::interface, "GetSubTree",
705 "/xyz/openbmc_project/inventory/system", 2, allInterfaces);
706 }
707
readFile(const std::string & thresholdFile,const double & scaleFactor)708 std::optional<double> readFile(const std::string& thresholdFile,
709 const double& scaleFactor)
710 {
711 std::string line;
712 std::ifstream labelFile(thresholdFile);
713 if (labelFile.good())
714 {
715 std::getline(labelFile, line);
716 labelFile.close();
717
718 try
719 {
720 return std::stod(line) / scaleFactor;
721 }
722 catch (const std::invalid_argument&)
723 {
724 return std::nullopt;
725 }
726 }
727 return std::nullopt;
728 }
729
splitFileName(const std::filesystem::path & filePath)730 std::optional<std::tuple<std::string, std::string, std::string>> splitFileName(
731 const std::filesystem::path& filePath)
732 {
733 if (filePath.has_filename())
734 {
735 const auto fileName = filePath.filename().string();
736
737 size_t numberPos = std::strcspn(fileName.c_str(), "1234567890");
738 size_t itemPos = std::strcspn(fileName.c_str(), "_");
739
740 if (numberPos > 0 && itemPos > numberPos && fileName.size() > itemPos)
741 {
742 return std::make_optional(
743 std::make_tuple(fileName.substr(0, numberPos),
744 fileName.substr(numberPos, itemPos - numberPos),
745 fileName.substr(itemPos + 1, fileName.size())));
746 }
747 }
748 return std::nullopt;
749 }
750
handleSpecialModeChange(const std::string & manufacturingModeStatus)751 static void handleSpecialModeChange(const std::string& manufacturingModeStatus)
752 {
753 manufacturingMode = false;
754 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
755 "SpecialMode.Modes.Manufacturing")
756 {
757 manufacturingMode = true;
758 }
759 if (validateUnsecureFeature == 1)
760 {
761 if (manufacturingModeStatus == "xyz.openbmc_project.Control.Security."
762 "SpecialMode.Modes.ValidationUnsecure")
763 {
764 manufacturingMode = true;
765 }
766 }
767 }
768
setupManufacturingModeMatch(sdbusplus::asio::connection & conn)769 void setupManufacturingModeMatch(sdbusplus::asio::connection& conn)
770 {
771 namespace rules = sdbusplus::bus::match::rules;
772 static constexpr const char* specialModeInterface =
773 "xyz.openbmc_project.Security.SpecialMode";
774
775 const std::string filterSpecialModeIntfAdd =
776 rules::interfacesAdded() +
777 rules::argNpath(0, "/xyz/openbmc_project/security/special_mode");
778 static std::unique_ptr<sdbusplus::bus::match_t> specialModeIntfMatch =
779 std::make_unique<sdbusplus::bus::match_t>(
780 conn, filterSpecialModeIntfAdd, [](sdbusplus::message_t& m) {
781 sdbusplus::message::object_path path;
782 using PropertyMap =
783 boost::container::flat_map<std::string,
784 std::variant<std::string>>;
785 boost::container::flat_map<std::string, PropertyMap>
786 interfaceAdded;
787 m.read(path, interfaceAdded);
788 auto intfItr = interfaceAdded.find(specialModeInterface);
789 if (intfItr == interfaceAdded.end())
790 {
791 return;
792 }
793 PropertyMap& propertyList = intfItr->second;
794 auto itr = propertyList.find("SpecialMode");
795 if (itr == propertyList.end())
796 {
797 lg2::error("error getting SpecialMode property");
798 return;
799 }
800 auto* manufacturingModeStatus =
801 std::get_if<std::string>(&itr->second);
802 handleSpecialModeChange(*manufacturingModeStatus);
803 });
804
805 const std::string filterSpecialModeChange =
806 rules::type::signal() + rules::member("PropertiesChanged") +
807 rules::interface("org.freedesktop.DBus.Properties") +
808 rules::argN(0, specialModeInterface);
809 static std::unique_ptr<sdbusplus::bus::match_t> specialModeChangeMatch =
810 std::make_unique<sdbusplus::bus::match_t>(
811 conn, filterSpecialModeChange, [](sdbusplus::message_t& m) {
812 std::string interfaceName;
813 boost::container::flat_map<std::string,
814 std::variant<std::string>>
815 propertiesChanged;
816
817 m.read(interfaceName, propertiesChanged);
818 auto itr = propertiesChanged.find("SpecialMode");
819 if (itr == propertiesChanged.end())
820 {
821 return;
822 }
823 auto* manufacturingModeStatus =
824 std::get_if<std::string>(&itr->second);
825 handleSpecialModeChange(*manufacturingModeStatus);
826 });
827
828 conn.async_method_call(
829 [](const boost::system::error_code ec,
830 const std::variant<std::string>& getManufactMode) {
831 if (ec)
832 {
833 lg2::error(
834 "error getting SpecialMode status: '{ERROR_MESSAGE}'",
835 "ERROR_MESSAGE", ec.message());
836 return;
837 }
838 const auto* manufacturingModeStatus =
839 std::get_if<std::string>(&getManufactMode);
840 handleSpecialModeChange(*manufacturingModeStatus);
841 },
842 "xyz.openbmc_project.SpecialMode",
843 "/xyz/openbmc_project/security/special_mode",
844 "org.freedesktop.DBus.Properties", "Get", specialModeInterface,
845 "SpecialMode");
846 }
847
getManufacturingMode()848 bool getManufacturingMode()
849 {
850 return manufacturingMode;
851 }
852
853 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)854 setupPropertiesChangedMatches(
855 sdbusplus::asio::connection& bus, std::span<const char* const> types,
856 const std::function<void(sdbusplus::message_t&)>& handler)
857 {
858 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches;
859 for (const char* type : types)
860 {
861 auto match = std::make_unique<sdbusplus::bus::match_t>(
862 static_cast<sdbusplus::bus_t&>(bus),
863 "type='signal',member='PropertiesChanged',path_namespace='" +
864 std::string(inventoryPath) + "',arg0namespace='" +
865 configInterfaceName(type) + "'",
866 handler);
867 matches.emplace_back(std::move(match));
868 }
869 return matches;
870 }
871
872 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)873 setupPropertiesChangedMatches(
874 sdbusplus::asio::connection& bus, const I2CDeviceTypeMap& typeMap,
875 const std::function<void(sdbusplus::message_t&)>& handler)
876 {
877 std::vector<const char*> types;
878 types.reserve(typeMap.size());
879 for (const auto& [type, dt] : typeMap)
880 {
881 types.push_back(type.data());
882 }
883 return setupPropertiesChangedMatches(bus, {types}, handler);
884 }
885