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