1 /** 2 * Copyright © 2016 IBM 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 "config.h" 17 18 #include "mainloop.hpp" 19 20 #include "env.hpp" 21 #include "fan_pwm.hpp" 22 #include "fan_speed.hpp" 23 #include "hwmon.hpp" 24 #include "hwmonio.hpp" 25 #include "sensor.hpp" 26 #include "sensorset.hpp" 27 #include "sysfs.hpp" 28 #include "targets.hpp" 29 #include "thresholds.hpp" 30 #include "util.hpp" 31 32 #include <fmt/format.h> 33 34 #include <cassert> 35 #include <cstdlib> 36 #include <functional> 37 #include <future> 38 #include <iostream> 39 #include <memory> 40 #include <phosphor-logging/elog-errors.hpp> 41 #include <sstream> 42 #include <string> 43 #include <unordered_set> 44 #include <xyz/openbmc_project/Sensor/Device/error.hpp> 45 46 using namespace phosphor::logging; 47 48 // Initialization for Warning Objects 49 decltype(Thresholds<WarningObject>::setLo) Thresholds<WarningObject>::setLo = 50 &WarningObject::warningLow; 51 decltype(Thresholds<WarningObject>::setHi) Thresholds<WarningObject>::setHi = 52 &WarningObject::warningHigh; 53 decltype(Thresholds<WarningObject>::getLo) Thresholds<WarningObject>::getLo = 54 &WarningObject::warningLow; 55 decltype(Thresholds<WarningObject>::getHi) Thresholds<WarningObject>::getHi = 56 &WarningObject::warningHigh; 57 decltype(Thresholds<WarningObject>::alarmLo) 58 Thresholds<WarningObject>::alarmLo = &WarningObject::warningAlarmLow; 59 decltype(Thresholds<WarningObject>::alarmHi) 60 Thresholds<WarningObject>::alarmHi = &WarningObject::warningAlarmHigh; 61 decltype(Thresholds<WarningObject>::getAlarmLow) 62 Thresholds<WarningObject>::getAlarmLow = &WarningObject::warningAlarmLow; 63 decltype(Thresholds<WarningObject>::getAlarmHigh) 64 Thresholds<WarningObject>::getAlarmHigh = &WarningObject::warningAlarmHigh; 65 decltype(Thresholds<WarningObject>::assertLowSignal) 66 Thresholds<WarningObject>::assertLowSignal = 67 &WarningObject::warningLowAlarmAsserted; 68 decltype(Thresholds<WarningObject>::assertHighSignal) 69 Thresholds<WarningObject>::assertHighSignal = 70 &WarningObject::warningHighAlarmAsserted; 71 decltype(Thresholds<WarningObject>::deassertLowSignal) 72 Thresholds<WarningObject>::deassertLowSignal = 73 &WarningObject::warningLowAlarmDeasserted; 74 decltype(Thresholds<WarningObject>::deassertHighSignal) 75 Thresholds<WarningObject>::deassertHighSignal = 76 &WarningObject::warningHighAlarmDeasserted; 77 78 // Initialization for Critical Objects 79 decltype(Thresholds<CriticalObject>::setLo) Thresholds<CriticalObject>::setLo = 80 &CriticalObject::criticalLow; 81 decltype(Thresholds<CriticalObject>::setHi) Thresholds<CriticalObject>::setHi = 82 &CriticalObject::criticalHigh; 83 decltype(Thresholds<CriticalObject>::getLo) Thresholds<CriticalObject>::getLo = 84 &CriticalObject::criticalLow; 85 decltype(Thresholds<CriticalObject>::getHi) Thresholds<CriticalObject>::getHi = 86 &CriticalObject::criticalHigh; 87 decltype(Thresholds<CriticalObject>::alarmLo) 88 Thresholds<CriticalObject>::alarmLo = &CriticalObject::criticalAlarmLow; 89 decltype(Thresholds<CriticalObject>::alarmHi) 90 Thresholds<CriticalObject>::alarmHi = &CriticalObject::criticalAlarmHigh; 91 decltype(Thresholds<CriticalObject>::getAlarmLow) 92 Thresholds<CriticalObject>::getAlarmLow = &CriticalObject::criticalAlarmLow; 93 decltype(Thresholds<CriticalObject>::getAlarmHigh) 94 Thresholds<CriticalObject>::getAlarmHigh = 95 &CriticalObject::criticalAlarmHigh; 96 decltype(Thresholds<CriticalObject>::assertLowSignal) 97 Thresholds<CriticalObject>::assertLowSignal = 98 &CriticalObject::criticalLowAlarmAsserted; 99 decltype(Thresholds<CriticalObject>::assertHighSignal) 100 Thresholds<CriticalObject>::assertHighSignal = 101 &CriticalObject::criticalHighAlarmAsserted; 102 decltype(Thresholds<CriticalObject>::deassertLowSignal) 103 Thresholds<CriticalObject>::deassertLowSignal = 104 &CriticalObject::criticalLowAlarmDeasserted; 105 decltype(Thresholds<CriticalObject>::deassertHighSignal) 106 Thresholds<CriticalObject>::deassertHighSignal = 107 &CriticalObject::criticalHighAlarmDeasserted; 108 109 void updateSensorInterfaces(InterfaceMap& ifaces, SensorValueType value) 110 { 111 for (auto& iface : ifaces) 112 { 113 switch (iface.first) 114 { 115 // clang-format off 116 case InterfaceType::VALUE: 117 { 118 auto& valueIface = 119 std::any_cast<std::shared_ptr<ValueObject>&>(iface.second); 120 valueIface->value(value); 121 } 122 break; 123 // clang-format on 124 case InterfaceType::WARN: 125 checkThresholds<WarningObject>(iface.second, value); 126 break; 127 case InterfaceType::CRIT: 128 checkThresholds<CriticalObject>(iface.second, value); 129 break; 130 default: 131 break; 132 } 133 } 134 } 135 136 std::string MainLoop::getID(SensorSet::container_t::const_reference sensor) 137 { 138 std::string id; 139 140 /* 141 * Check if the value of the MODE_<item><X> env variable for the sensor 142 * is set. If it is, then read the from the <item><X>_<mode> 143 * file. The name of the DBUS object would be the value of the env 144 * variable LABEL_<item><mode value>. If the MODE_<item><X> env variable 145 * doesn't exist, then the name of DBUS object is the value of the env 146 * variable LABEL_<item><X>. 147 * 148 * For example, if MODE_temp1 = "label", then code reads the temp1_label 149 * file. If it has a 5 in it, then it will use the following entry to 150 * name the object: LABEL_temp5 = "My DBus object name". 151 * 152 */ 153 auto mode = env::getEnv("MODE", sensor.first); 154 if (!mode.empty()) 155 { 156 id = env::getIndirectID(_hwmonRoot + '/' + _instance + '/', mode, 157 sensor.first); 158 159 if (id.empty()) 160 { 161 return id; 162 } 163 } 164 165 // Use the ID we looked up above if there was one, 166 // otherwise use the standard one. 167 id = (id.empty()) ? sensor.first.second : id; 168 169 return id; 170 } 171 172 SensorIdentifiers 173 MainLoop::getIdentifiers(SensorSet::container_t::const_reference sensor) 174 { 175 std::string id = getID(sensor); 176 std::string label; 177 std::string accuracy; 178 179 if (!id.empty()) 180 { 181 // Ignore inputs without a label. 182 label = env::getEnv("LABEL", sensor.first.first, id); 183 accuracy = env::getEnv("ACCURACY", sensor.first.first, id); 184 } 185 186 return std::make_tuple(std::move(id), std::move(label), 187 std::move(accuracy)); 188 } 189 190 /** 191 * Reads the environment parameters of a sensor and creates an object with 192 * atleast the `Value` interface, otherwise returns without creating the object. 193 * If the `Value` interface is successfully created, by reading the sensor's 194 * corresponding sysfs file's value, the additional interfaces for the sensor 195 * are created and the InterfacesAdded signal is emitted. The object's state 196 * data is then returned for sensor state monitoring within the main loop. 197 */ 198 std::optional<ObjectStateData> 199 MainLoop::getObject(SensorSet::container_t::const_reference sensor) 200 { 201 auto properties = getIdentifiers(sensor); 202 if (std::get<sensorID>(properties).empty() || 203 std::get<sensorLabel>(properties).empty()) 204 { 205 return {}; 206 } 207 208 hwmon::Attributes attrs; 209 if (!hwmon::getAttributes(sensor.first.first, attrs)) 210 { 211 return {}; 212 } 213 214 const auto& [sensorSetKey, sensorAttrs] = sensor; 215 const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey; 216 217 /* Note: The sensor objects all share the same ioAccess object. */ 218 auto sensorObj = 219 std::make_unique<sensor::Sensor>(sensorSetKey, _ioAccess, _devPath); 220 221 // Get list of return codes for removing sensors on device 222 auto devRmRCs = env::getEnv("REMOVERCS"); 223 // Add sensor removal return codes defined at the device level 224 sensorObj->addRemoveRCs(devRmRCs); 225 226 std::string objectPath{_root}; 227 objectPath.append(1, '/'); 228 objectPath.append(hwmon::getNamespace(attrs)); 229 objectPath.append(1, '/'); 230 objectPath.append(std::get<sensorLabel>(properties)); 231 232 ObjectInfo info(&_bus, std::move(objectPath), InterfaceMap()); 233 RetryIO retryIO(hwmonio::retries, hwmonio::delay); 234 if (_rmSensors.find(sensorSetKey) != _rmSensors.end()) 235 { 236 // When adding a sensor that was purposely removed, 237 // don't retry on errors when reading its value 238 std::get<size_t>(retryIO) = 0; 239 } 240 auto valueInterface = static_cast<std::shared_ptr<ValueObject>>(nullptr); 241 try 242 { 243 // Add accuracy interface 244 auto accuracyStr = std::get<sensorAccuracy>(properties); 245 try 246 { 247 if (!accuracyStr.empty()) 248 { 249 auto accuracy = stod(accuracyStr); 250 sensorObj->addAccuracy(info, accuracy); 251 } 252 } 253 catch (const std::invalid_argument&) 254 { 255 } 256 257 // Add status interface based on _fault file being present 258 sensorObj->addStatus(info); 259 valueInterface = sensorObj->addValue(retryIO, info, _timedoutMap); 260 } 261 catch (const std::system_error& e) 262 { 263 auto file = 264 sysfs::make_sysfs_path(_ioAccess->path(), sensorSysfsType, 265 sensorSysfsNum, hwmon::entry::cinput); 266 267 // Check sensorAdjusts for sensor removal RCs 268 auto& sAdjusts = sensorObj->getAdjusts(); 269 if (sAdjusts.rmRCs.count(e.code().value()) > 0) 270 { 271 // Return code found in sensor return code removal list 272 if (_rmSensors.find(sensorSetKey) == _rmSensors.end()) 273 { 274 // Trace for sensor not already removed from dbus 275 log<level::INFO>("Sensor not added to dbus for read fail", 276 entry("FILE=%s", file.c_str()), 277 entry("RC=%d", e.code().value())); 278 _rmSensors[std::move(sensorSetKey)] = std::move(sensorAttrs); 279 } 280 return {}; 281 } 282 283 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error; 284 report<ReadFailure>( 285 xyz::openbmc_project::Sensor::Device::ReadFailure::CALLOUT_ERRNO( 286 e.code().value()), 287 xyz::openbmc_project::Sensor::Device::ReadFailure:: 288 CALLOUT_DEVICE_PATH(_devPath.c_str())); 289 290 log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}", file, 291 e.code().value()) 292 .c_str()); 293 exit(EXIT_FAILURE); 294 } 295 auto sensorValue = valueInterface->value(); 296 int64_t scale = sensorObj->getScale(); 297 298 addThreshold<WarningObject>(sensorSysfsType, std::get<sensorID>(properties), 299 sensorValue, info, scale); 300 addThreshold<CriticalObject>(sensorSysfsType, 301 std::get<sensorID>(properties), sensorValue, 302 info, scale); 303 304 auto target = 305 addTarget<hwmon::FanSpeed>(sensorSetKey, _ioAccess, _devPath, info); 306 if (target) 307 { 308 target->enable(); 309 } 310 addTarget<hwmon::FanPwm>(sensorSetKey, _ioAccess, _devPath, info); 311 312 // All the interfaces have been created. Go ahead 313 // and emit InterfacesAdded. 314 valueInterface->emit_object_added(); 315 316 // Save sensor object specifications 317 _sensorObjects[sensorSetKey] = std::move(sensorObj); 318 319 return std::make_pair(std::move(std::get<sensorLabel>(properties)), 320 std::move(info)); 321 } 322 323 MainLoop::MainLoop(sdbusplus::bus_t&& bus, const std::string& param, 324 const std::string& path, const std::string& devPath, 325 const char* prefix, const char* root, 326 const std::string& instanceId, 327 const hwmonio::HwmonIOInterface* ioIntf) : 328 _bus(std::move(bus)), 329 _manager(_bus, root), _pathParam(param), _hwmonRoot(), _instance(), 330 _devPath(devPath), _prefix(prefix), _root(root), _state(), 331 _instanceId(instanceId), _ioAccess(ioIntf), 332 _event(sdeventplus::Event::get_default()), 333 _timer(_event, std::bind(&MainLoop::read, this)) 334 { 335 // Strip off any trailing slashes. 336 std::string p = path; 337 while (!p.empty() && p.back() == '/') 338 { 339 p.pop_back(); 340 } 341 342 // Given the furthest right /, set instance to 343 // the basename, and hwmonRoot to the leading path. 344 auto n = p.rfind('/'); 345 if (n != std::string::npos) 346 { 347 _instance.assign(p.substr(n + 1)); 348 _hwmonRoot.assign(p.substr(0, n)); 349 } 350 351 assert(!_instance.empty()); 352 assert(!_hwmonRoot.empty()); 353 } 354 355 void MainLoop::shutdown() noexcept 356 { 357 _event.exit(0); 358 } 359 360 void MainLoop::run() 361 { 362 init(); 363 364 std::function<void()> callback(std::bind(&MainLoop::read, this)); 365 try 366 { 367 _timer.restart(std::chrono::microseconds(_interval)); 368 369 // TODO: Issue#6 - Optionally look at polling interval sysfs entry. 370 371 // TODO: Issue#7 - Should probably periodically check the SensorSet 372 // for new entries. 373 374 _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT); 375 _event.loop(); 376 } 377 catch (const std::exception& e) 378 { 379 log<level::ERR>("Error in sysfs polling loop", 380 entry("ERROR=%s", e.what())); 381 throw; 382 } 383 } 384 385 void MainLoop::init() 386 { 387 // Check sysfs for available sensors. 388 auto sensors = std::make_unique<SensorSet>(_hwmonRoot + '/' + _instance); 389 390 for (const auto& i : *sensors) 391 { 392 auto object = getObject(i); 393 if (object) 394 { 395 // Construct the SensorSet value 396 // std::tuple<SensorSet::mapped_type, 397 // std::string(Sensor Label), 398 // ObjectInfo> 399 auto value = 400 std::make_tuple(std::move(i.second), std::move((*object).first), 401 std::move((*object).second)); 402 403 _state[std::move(i.first)] = std::move(value); 404 } 405 406 // Initialize _averageMap of sensor. e.g. <<power, 1>, <0, 0>> 407 if ((i.first.first == hwmon::type::power) && 408 (phosphor::utility::isAverageEnvSet(i.first))) 409 { 410 _average.setAverageValue(i.first, std::make_pair(0, 0)); 411 } 412 } 413 414 /* If there are no sensors specified by labels, exit. */ 415 if (0 == _state.size()) 416 { 417 exit(0); 418 } 419 420 { 421 std::stringstream ss; 422 std::string id = _instanceId; 423 if (id.empty()) 424 { 425 id = 426 std::to_string(std::hash<std::string>{}(_devPath + _pathParam)); 427 } 428 ss << _prefix << "-" << id << ".Hwmon1"; 429 430 _bus.request_name(ss.str().c_str()); 431 } 432 433 { 434 auto interval = env::getEnv("INTERVAL"); 435 if (!interval.empty()) 436 { 437 _interval = std::strtoull(interval.c_str(), NULL, 10); 438 } 439 } 440 } 441 442 void MainLoop::read() 443 { 444 // TODO: Issue#3 - Need to make calls to the dbus sensor cache here to 445 // ensure the objects all exist? 446 447 // Iterate through all the sensors. 448 for (auto& [sensorSetKey, sensorStateTuple] : _state) 449 { 450 const auto& [sensorSysfsType, sensorSysfsNum] = sensorSetKey; 451 auto& [attrs, unused, objInfo] = sensorStateTuple; 452 453 if (attrs.find(hwmon::entry::input) == attrs.end()) 454 { 455 continue; 456 } 457 458 // Read value from sensor. 459 std::string input = hwmon::entry::input; 460 if (sensorSysfsType == hwmon::type::pwm) 461 { 462 input = ""; 463 } 464 // If type is power and AVERAGE_power* is true in env, use average 465 // instead of input 466 else if ((sensorSysfsType == hwmon::type::power) && 467 (phosphor::utility::isAverageEnvSet(sensorSetKey))) 468 { 469 input = hwmon::entry::average; 470 } 471 472 SensorValueType value; 473 auto& obj = std::get<InterfaceMap>(objInfo); 474 std::unique_ptr<sensor::Sensor>& sensor = _sensorObjects[sensorSetKey]; 475 476 auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>( 477 obj[InterfaceType::STATUS]); 478 // As long as addStatus is called before addValue, statusIface 479 // should never be nullptr. 480 assert(statusIface); 481 482 try 483 { 484 if (sensor->hasFaultFile()) 485 { 486 auto fault = _ioAccess->read(sensorSysfsType, sensorSysfsNum, 487 hwmon::entry::fault, 488 hwmonio::retries, hwmonio::delay); 489 // Skip reading from a sensor with a valid fault file 490 // and set the functional property accordingly 491 if (!statusIface->functional((fault == 0) ? true : false)) 492 { 493 continue; 494 } 495 } 496 497 { 498 // RAII object for GPIO unlock / lock 499 auto locker = sensor::gpioUnlock(sensor->getGpio()); 500 501 // For sensors with attribute ASYNC_READ_TIMEOUT, 502 // spawn a thread with timeout 503 auto asyncReadTimeout = 504 env::getEnv("ASYNC_READ_TIMEOUT", sensorSetKey); 505 if (!asyncReadTimeout.empty()) 506 { 507 std::chrono::milliseconds asyncTimeout{ 508 std::stoi(asyncReadTimeout)}; 509 value = sensor::asyncRead( 510 sensorSetKey, _ioAccess, asyncTimeout, _timedoutMap, 511 sensorSysfsType, sensorSysfsNum, input, 512 hwmonio::retries, hwmonio::delay); 513 } 514 else 515 { 516 // Retry for up to a second if device is busy 517 // or has a transient error. 518 value = 519 _ioAccess->read(sensorSysfsType, sensorSysfsNum, input, 520 hwmonio::retries, hwmonio::delay); 521 } 522 523 // Set functional property to true if we could read sensor 524 statusIface->functional(true); 525 526 value = sensor->adjustValue(value); 527 528 if (input == hwmon::entry::average) 529 { 530 // Calculate the values of averageMap based on current 531 // average value, current average_interval value, previous 532 // average value, previous average_interval value 533 int64_t interval = 534 _ioAccess->read(sensorSysfsType, sensorSysfsNum, 535 hwmon::entry::caverage_interval, 536 hwmonio::retries, hwmonio::delay); 537 auto ret = _average.getAverageValue(sensorSetKey); 538 assert(ret); 539 540 const auto& [preAverage, preInterval] = *ret; 541 542 auto calValue = Average::calcAverage( 543 preAverage, preInterval, value, interval); 544 if (calValue) 545 { 546 // Update previous values in averageMap before the 547 // variable value is changed next 548 _average.setAverageValue( 549 sensorSetKey, std::make_pair(value, interval)); 550 // Update value to be calculated average 551 value = calValue.value(); 552 } 553 else 554 { 555 // the value of 556 // power*_average_interval is not changed yet, use the 557 // previous calculated average instead. So skip dbus 558 // update. 559 continue; 560 } 561 } 562 } 563 564 updateSensorInterfaces(obj, value); 565 } 566 catch (const std::system_error& e) 567 { 568 #if UPDATE_FUNCTIONAL_ON_FAIL 569 // If UPDATE_FUNCTIONAL_ON_FAIL is defined and an exception was 570 // thrown, set the functional property to false. 571 // We cannot set this with the 'continue' in the lower block 572 // as the code may exit before reaching it. 573 statusIface->functional(false); 574 #endif 575 auto file = sysfs::make_sysfs_path( 576 _ioAccess->path(), sensorSysfsType, sensorSysfsNum, input); 577 578 // Check sensorAdjusts for sensor removal RCs 579 auto& sAdjusts = _sensorObjects[sensorSetKey]->getAdjusts(); 580 if (sAdjusts.rmRCs.count(e.code().value()) > 0) 581 { 582 // Return code found in sensor return code removal list 583 if (_rmSensors.find(sensorSetKey) == _rmSensors.end()) 584 { 585 // Trace for sensor not already removed from dbus 586 log<level::INFO>("Remove sensor from dbus for read fail", 587 entry("FILE=%s", file.c_str()), 588 entry("RC=%d", e.code().value())); 589 // Mark this sensor to be removed from dbus 590 _rmSensors[sensorSetKey] = attrs; 591 } 592 continue; 593 } 594 #if UPDATE_FUNCTIONAL_ON_FAIL 595 // Do not exit with failure if UPDATE_FUNCTIONAL_ON_FAIL is set 596 continue; 597 #endif 598 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device:: 599 Error; 600 report<ReadFailure>( 601 xyz::openbmc_project::Sensor::Device::ReadFailure:: 602 CALLOUT_ERRNO(e.code().value()), 603 xyz::openbmc_project::Sensor::Device::ReadFailure:: 604 CALLOUT_DEVICE_PATH(_devPath.c_str())); 605 606 log<level::INFO>(fmt::format("Failing sysfs file: {} errno: {}", 607 file, e.code().value()) 608 .c_str()); 609 610 exit(EXIT_FAILURE); 611 } 612 } 613 614 removeSensors(); 615 616 addDroppedSensors(); 617 } 618 619 void MainLoop::removeSensors() 620 { 621 // Remove any sensors marked for removal 622 for (const auto& i : _rmSensors) 623 { 624 // Remove sensor object from dbus using emit_object_removed() 625 auto& objInfo = std::get<ObjectInfo>(_state[i.first]); 626 auto& objPath = std::get<std::string>(objInfo); 627 628 _bus.emit_object_removed(objPath.c_str()); 629 630 // Erase sensor object info 631 _state.erase(i.first); 632 } 633 } 634 635 void MainLoop::addDroppedSensors() 636 { 637 // Attempt to add any sensors that were removed 638 auto it = _rmSensors.begin(); 639 while (it != _rmSensors.end()) 640 { 641 if (_state.find(it->first) == _state.end()) 642 { 643 SensorSet::container_t::value_type ssValueType = 644 std::make_pair(it->first, it->second); 645 646 auto object = getObject(ssValueType); 647 if (object) 648 { 649 // Construct the SensorSet value 650 // std::tuple<SensorSet::mapped_type, 651 // std::string(Sensor Label), 652 // ObjectInfo> 653 auto value = std::make_tuple(std::move(ssValueType.second), 654 std::move((*object).first), 655 std::move((*object).second)); 656 657 _state[std::move(ssValueType.first)] = std::move(value); 658 659 std::string input = hwmon::entry::input; 660 // If type is power and AVERAGE_power* is true in env, use 661 // average instead of input 662 if ((it->first.first == hwmon::type::power) && 663 (phosphor::utility::isAverageEnvSet(it->first))) 664 { 665 input = hwmon::entry::average; 666 } 667 // Sensor object added, erase entry from removal list 668 auto file = 669 sysfs::make_sysfs_path(_ioAccess->path(), it->first.first, 670 it->first.second, input); 671 672 log<level::INFO>("Added sensor to dbus after successful read", 673 entry("FILE=%s", file.c_str())); 674 675 it = _rmSensors.erase(it); 676 } 677 else 678 { 679 ++it; 680 } 681 } 682 else 683 { 684 // Sanity check to remove sensors that were re-added 685 it = _rmSensors.erase(it); 686 } 687 } 688 } 689 690 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 691