16714a25aSJames Feist /* 26714a25aSJames Feist // Copyright (c) 2017 Intel Corporation 36714a25aSJames Feist // 46714a25aSJames Feist // Licensed under the Apache License, Version 2.0 (the "License"); 56714a25aSJames Feist // you may not use this file except in compliance with the License. 66714a25aSJames Feist // You may obtain a copy of the License at 76714a25aSJames Feist // 86714a25aSJames Feist // http://www.apache.org/licenses/LICENSE-2.0 96714a25aSJames Feist // 106714a25aSJames Feist // Unless required by applicable law or agreed to in writing, software 116714a25aSJames Feist // distributed under the License is distributed on an "AS IS" BASIS, 126714a25aSJames Feist // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136714a25aSJames Feist // See the License for the specific language governing permissions and 146714a25aSJames Feist // limitations under the License. 156714a25aSJames Feist */ 166714a25aSJames Feist 176714a25aSJames Feist #include <PwmSensor.hpp> 186714a25aSJames Feist #include <TachSensor.hpp> 196714a25aSJames Feist #include <Utils.hpp> 206714a25aSJames Feist #include <VariantVisitors.hpp> 216714a25aSJames Feist #include <boost/algorithm/string/predicate.hpp> 226714a25aSJames Feist #include <boost/algorithm/string/replace.hpp> 236714a25aSJames Feist #include <boost/container/flat_set.hpp> 246714a25aSJames Feist #include <boost/lexical_cast.hpp> 256714a25aSJames Feist #include <experimental/filesystem> 266714a25aSJames Feist #include <fstream> 276714a25aSJames Feist #include <regex> 286714a25aSJames Feist #include <sdbusplus/asio/connection.hpp> 296714a25aSJames Feist #include <sdbusplus/asio/object_server.hpp> 306714a25aSJames Feist 316714a25aSJames Feist static constexpr bool DEBUG = false; 326714a25aSJames Feist 336714a25aSJames Feist namespace fs = std::experimental::filesystem; 345093805cSYoo, Jae Hyun namespace variant_ns = sdbusplus::message::variant_ns; 3595b079b7SJames Feist static constexpr std::array<const char*, 2> sensorTypes = { 3695b079b7SJames Feist "xyz.openbmc_project.Configuration.AspeedFan", 3795b079b7SJames Feist "xyz.openbmc_project.Configuration.I2CFan"}; 38dc6c55f3SJames Feist constexpr const char* redundancyConfiguration = 39dc6c55f3SJames Feist "xyz.openbmc_project.Configuration.FanRedundancy"; 409ced0a38SJae Hyun Yoo static std::regex inputRegex(R"(fan(\d+)_input)"); 416714a25aSJames Feist 4295b079b7SJames Feist enum class FanTypes 4395b079b7SJames Feist { 4495b079b7SJames Feist aspeed, 4595b079b7SJames Feist i2c 4695b079b7SJames Feist }; 4795b079b7SJames Feist 48dc6c55f3SJames Feist // todo: power supply fan redundancy 4995b079b7SJames Feist std::shared_ptr<RedundancySensor> systemRedundancy = nullptr; 5095b079b7SJames Feist 5195b079b7SJames Feist FanTypes getFanType(const fs::path& parentPath) 5295b079b7SJames Feist { 5395b079b7SJames Feist fs::path linkPath = parentPath / "device"; 5495b079b7SJames Feist std::string canonical = fs::read_symlink(linkPath); 5595b079b7SJames Feist if (boost::ends_with(canonical, "1e786000.pwm-tacho-controller")) 5695b079b7SJames Feist { 5795b079b7SJames Feist return FanTypes::aspeed; 5895b079b7SJames Feist } 5995b079b7SJames Feist // todo: will we need to support other types? 6095b079b7SJames Feist return FanTypes::i2c; 6195b079b7SJames Feist } 62dc6c55f3SJames Feist 636714a25aSJames Feist void createSensors( 646714a25aSJames Feist boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer, 656714a25aSJames Feist boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>& 666714a25aSJames Feist tachSensors, 676714a25aSJames Feist boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>& 686714a25aSJames Feist pwmSensors, 696714a25aSJames Feist std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 706714a25aSJames Feist const std::unique_ptr<boost::container::flat_set<std::string>>& 716714a25aSJames Feist sensorsChanged) 726714a25aSJames Feist { 736714a25aSJames Feist bool firstScan = sensorsChanged == nullptr; 746714a25aSJames Feist // use new data the first time, then refresh 756714a25aSJames Feist ManagedObjectType sensorConfigurations; 766714a25aSJames Feist bool useCache = false; 779ced0a38SJae Hyun Yoo for (const char* type : sensorTypes) 786714a25aSJames Feist { 796714a25aSJames Feist if (!getSensorConfiguration(type, dbusConnection, sensorConfigurations, 806714a25aSJames Feist useCache)) 816714a25aSJames Feist { 826714a25aSJames Feist std::cerr << "error communicating to entity manager\n"; 836714a25aSJames Feist return; 846714a25aSJames Feist } 856714a25aSJames Feist useCache = true; 866714a25aSJames Feist } 876714a25aSJames Feist std::vector<fs::path> paths; 889ced0a38SJae Hyun Yoo if (!findFiles(fs::path("/sys/class/hwmon"), R"(fan\d+_input)", paths)) 896714a25aSJames Feist { 906714a25aSJames Feist std::cerr << "No temperature sensors in system\n"; 916714a25aSJames Feist return; 926714a25aSJames Feist } 936714a25aSJames Feist 946714a25aSJames Feist // iterate through all found fan sensors, and try to match them with 956714a25aSJames Feist // configuration 9695b079b7SJames Feist for (const auto& path : paths) 976714a25aSJames Feist { 986714a25aSJames Feist std::smatch match; 996714a25aSJames Feist std::string pathStr = path.string(); 1006714a25aSJames Feist 1019ced0a38SJae Hyun Yoo std::regex_search(pathStr, match, inputRegex); 1026714a25aSJames Feist std::string indexStr = *(match.begin() + 1); 1036714a25aSJames Feist 1046714a25aSJames Feist auto directory = path.parent_path(); 10595b079b7SJames Feist FanTypes fanType = getFanType(directory); 10695b079b7SJames Feist size_t bus = 0; 10795b079b7SJames Feist size_t address = 0; 10895b079b7SJames Feist if (fanType == FanTypes::i2c) 10995b079b7SJames Feist { 11095b079b7SJames Feist std::string link = 11195b079b7SJames Feist fs::read_symlink(directory / "device").filename(); 11295b079b7SJames Feist 11395b079b7SJames Feist size_t findDash = link.find("-"); 11495b079b7SJames Feist if (findDash == std::string::npos || link.size() <= findDash + 1) 11595b079b7SJames Feist { 11695b079b7SJames Feist std::cerr << "Error finding device from symlink"; 11795b079b7SJames Feist } 11895b079b7SJames Feist bus = std::stoi(link.substr(0, findDash)); 11995b079b7SJames Feist address = std::stoi(link.substr(findDash + 1), nullptr, 16); 12095b079b7SJames Feist } 1216714a25aSJames Feist // convert to 0 based 1226714a25aSJames Feist size_t index = std::stoul(indexStr) - 1; 1236714a25aSJames Feist 1246714a25aSJames Feist const char* baseType; 1256714a25aSJames Feist const SensorData* sensorData = nullptr; 1266714a25aSJames Feist const std::string* interfacePath = nullptr; 1276714a25aSJames Feist const std::pair<std::string, boost::container::flat_map< 1286714a25aSJames Feist std::string, BasicVariantType>>* 1296714a25aSJames Feist baseConfiguration = nullptr; 1306714a25aSJames Feist for (const std::pair<sdbusplus::message::object_path, SensorData>& 1316714a25aSJames Feist sensor : sensorConfigurations) 1326714a25aSJames Feist { 1336714a25aSJames Feist // find the base of the configuration to see if indexes match 1349ced0a38SJae Hyun Yoo for (const char* type : sensorTypes) 1356714a25aSJames Feist { 1366714a25aSJames Feist auto sensorBaseFind = sensor.second.find(type); 1376714a25aSJames Feist if (sensorBaseFind != sensor.second.end()) 1386714a25aSJames Feist { 1396714a25aSJames Feist baseConfiguration = &(*sensorBaseFind); 1406714a25aSJames Feist interfacePath = &(sensor.first.str); 1416714a25aSJames Feist baseType = type; 1426714a25aSJames Feist break; 1436714a25aSJames Feist } 1446714a25aSJames Feist } 1456714a25aSJames Feist if (baseConfiguration == nullptr) 1466714a25aSJames Feist { 1476714a25aSJames Feist continue; 1486714a25aSJames Feist } 1496714a25aSJames Feist auto findIndex = baseConfiguration->second.find("Index"); 1506714a25aSJames Feist if (findIndex == baseConfiguration->second.end()) 1516714a25aSJames Feist { 1526714a25aSJames Feist std::cerr << baseConfiguration->first << " missing index\n"; 1536714a25aSJames Feist continue; 1546714a25aSJames Feist } 1555093805cSYoo, Jae Hyun unsigned int configIndex = variant_ns::visit( 1566714a25aSJames Feist VariantToUnsignedIntVisitor(), findIndex->second); 1576714a25aSJames Feist if (configIndex != index) 1586714a25aSJames Feist { 1596714a25aSJames Feist continue; 1606714a25aSJames Feist } 16195b079b7SJames Feist if (fanType == FanTypes::aspeed) 1626714a25aSJames Feist { 16395b079b7SJames Feist // there will be only 1 aspeed sensor object in sysfs, we found 16495b079b7SJames Feist // the fan 16595b079b7SJames Feist sensorData = &(sensor.second); 16695b079b7SJames Feist break; 16795b079b7SJames Feist } 16895b079b7SJames Feist else if (baseType == "xyz.openbmc_project.Configuration.I2CFan") 16995b079b7SJames Feist { 17095b079b7SJames Feist auto findBus = baseConfiguration->second.find("Bus"); 17195b079b7SJames Feist auto findAddress = baseConfiguration->second.find("Address"); 17295b079b7SJames Feist if (findBus == baseConfiguration->second.end() || 17395b079b7SJames Feist findAddress == baseConfiguration->second.end()) 17495b079b7SJames Feist { 17595b079b7SJames Feist std::cerr << baseConfiguration->first 17695b079b7SJames Feist << " missing bus or address\n"; 1776714a25aSJames Feist continue; 1786714a25aSJames Feist } 17995b079b7SJames Feist unsigned int configBus = variant_ns::visit( 18095b079b7SJames Feist VariantToUnsignedIntVisitor(), findBus->second); 18195b079b7SJames Feist unsigned int configAddress = variant_ns::visit( 18295b079b7SJames Feist VariantToUnsignedIntVisitor(), findAddress->second); 18395b079b7SJames Feist 18495b079b7SJames Feist if (configBus == bus && configAddress == configAddress) 1856714a25aSJames Feist { 1866714a25aSJames Feist sensorData = &(sensor.second); 1876714a25aSJames Feist break; 1886714a25aSJames Feist } 1896714a25aSJames Feist } 19095b079b7SJames Feist } 1916714a25aSJames Feist if (sensorData == nullptr) 1926714a25aSJames Feist { 1936714a25aSJames Feist std::cerr << "failed to find match for " << path.string() << "\n"; 1946714a25aSJames Feist continue; 1956714a25aSJames Feist } 1966714a25aSJames Feist 1976714a25aSJames Feist auto findSensorName = baseConfiguration->second.find("Name"); 1986714a25aSJames Feist if (findSensorName == baseConfiguration->second.end()) 1996714a25aSJames Feist { 2006714a25aSJames Feist std::cerr << "could not determine configuration name for " 2016714a25aSJames Feist << path.string() << "\n"; 2026714a25aSJames Feist continue; 2036714a25aSJames Feist } 2046714a25aSJames Feist std::string sensorName = 2056714a25aSJames Feist sdbusplus::message::variant_ns::get<std::string>( 2066714a25aSJames Feist findSensorName->second); 2076714a25aSJames Feist // on rescans, only update sensors we were signaled by 2086714a25aSJames Feist auto findSensor = tachSensors.find(sensorName); 2096714a25aSJames Feist if (!firstScan && findSensor != tachSensors.end()) 2106714a25aSJames Feist { 2116714a25aSJames Feist bool found = false; 2126714a25aSJames Feist for (auto it = sensorsChanged->begin(); it != sensorsChanged->end(); 2136714a25aSJames Feist it++) 2146714a25aSJames Feist { 2156714a25aSJames Feist if (boost::ends_with(*it, findSensor->second->name)) 2166714a25aSJames Feist { 2176714a25aSJames Feist sensorsChanged->erase(it); 2186714a25aSJames Feist findSensor->second = nullptr; 2196714a25aSJames Feist found = true; 2206714a25aSJames Feist break; 2216714a25aSJames Feist } 2226714a25aSJames Feist } 2236714a25aSJames Feist if (!found) 2246714a25aSJames Feist { 2256714a25aSJames Feist continue; 2266714a25aSJames Feist } 2276714a25aSJames Feist } 2286714a25aSJames Feist std::vector<thresholds::Threshold> sensorThresholds; 2299ced0a38SJae Hyun Yoo if (!parseThresholdsFromConfig(*sensorData, sensorThresholds)) 2306714a25aSJames Feist { 2316714a25aSJames Feist std::cerr << "error populating thresholds for " << sensorName 2326714a25aSJames Feist << "\n"; 2336714a25aSJames Feist } 2346714a25aSJames Feist 2357bc2bab2SJames Feist auto presenceConfig = 2367bc2bab2SJames Feist sensorData->find(baseType + std::string(".Presence")); 2377bc2bab2SJames Feist 2387bc2bab2SJames Feist std::unique_ptr<PresenceSensor> presenceSensor(nullptr); 2397bc2bab2SJames Feist 2407bc2bab2SJames Feist // presence sensors are optional 2417bc2bab2SJames Feist if (presenceConfig != sensorData->end()) 2427bc2bab2SJames Feist { 2437bc2bab2SJames Feist auto findIndex = presenceConfig->second.find("Index"); 2447bc2bab2SJames Feist auto findPolarity = presenceConfig->second.find("Polarity"); 2457bc2bab2SJames Feist 2467bc2bab2SJames Feist if (findIndex == presenceConfig->second.end() || 2477bc2bab2SJames Feist findPolarity == presenceConfig->second.end()) 2487bc2bab2SJames Feist { 2497bc2bab2SJames Feist std::cerr << "Malformed Presence Configuration\n"; 2507bc2bab2SJames Feist } 2517bc2bab2SJames Feist else 2527bc2bab2SJames Feist { 2537bc2bab2SJames Feist size_t index = variant_ns::get<uint64_t>(findIndex->second); 2547bc2bab2SJames Feist bool inverted = 2557bc2bab2SJames Feist variant_ns::get<std::string>(findPolarity->second) == "Low"; 2567bc2bab2SJames Feist presenceSensor = 2577bc2bab2SJames Feist std::make_unique<PresenceSensor>(index, inverted, io); 2587bc2bab2SJames Feist } 2597bc2bab2SJames Feist } 26095b079b7SJames Feist std::shared_ptr<RedundancySensor> redundancy; 26195b079b7SJames Feist if (fanType == FanTypes::aspeed) 26295b079b7SJames Feist { 26395b079b7SJames Feist redundancy = systemRedundancy; 26495b079b7SJames Feist } 2657bc2bab2SJames Feist 2666714a25aSJames Feist tachSensors[sensorName] = std::make_unique<TachSensor>( 267*ce3fca41SJames Feist path.string(), baseType, objectServer, dbusConnection, 26895b079b7SJames Feist std::move(presenceSensor), redundancy, io, sensorName, 2696714a25aSJames Feist std::move(sensorThresholds), *interfacePath); 2706714a25aSJames Feist } 2716714a25aSJames Feist std::vector<fs::path> pwms; 2729ced0a38SJae Hyun Yoo if (!findFiles(fs::path("/sys/class/hwmon"), R"(pwm\d+)", pwms)) 2736714a25aSJames Feist { 2746714a25aSJames Feist std::cerr << "No pwm in system\n"; 2756714a25aSJames Feist return; 2766714a25aSJames Feist } 2776714a25aSJames Feist for (const fs::path& pwm : pwms) 2786714a25aSJames Feist { 27995b079b7SJames Feist if (pwmSensors.find(pwm) != pwmSensors.end()) 28095b079b7SJames Feist { 28195b079b7SJames Feist continue; 28295b079b7SJames Feist } 2836714a25aSJames Feist // only add new elements 2846714a25aSJames Feist pwmSensors.insert(std::pair<std::string, std::unique_ptr<PwmSensor>>( 2856714a25aSJames Feist pwm.string(), 2866714a25aSJames Feist std::make_unique<PwmSensor>(pwm.string(), objectServer))); 2876714a25aSJames Feist } 2886714a25aSJames Feist } 2896714a25aSJames Feist 290dc6c55f3SJames Feist void createRedundancySensor( 291dc6c55f3SJames Feist const boost::container::flat_map<std::string, std::unique_ptr<TachSensor>>& 292dc6c55f3SJames Feist sensors, 293dc6c55f3SJames Feist std::shared_ptr<sdbusplus::asio::connection> conn, 294dc6c55f3SJames Feist sdbusplus::asio::object_server& objectServer) 295dc6c55f3SJames Feist { 296dc6c55f3SJames Feist 297dc6c55f3SJames Feist conn->async_method_call( 298dc6c55f3SJames Feist [&objectServer, &sensors](boost::system::error_code& ec, 299dc6c55f3SJames Feist const ManagedObjectType managedObj) { 300dc6c55f3SJames Feist if (ec) 301dc6c55f3SJames Feist { 302dc6c55f3SJames Feist std::cerr << "Error calling entity manager \n"; 303dc6c55f3SJames Feist return; 304dc6c55f3SJames Feist } 305dc6c55f3SJames Feist for (const auto& pathPair : managedObj) 306dc6c55f3SJames Feist { 307dc6c55f3SJames Feist for (const auto& interfacePair : pathPair.second) 308dc6c55f3SJames Feist { 309dc6c55f3SJames Feist if (interfacePair.first == redundancyConfiguration) 310dc6c55f3SJames Feist { 311dc6c55f3SJames Feist // currently only support one 312dc6c55f3SJames Feist auto findCount = 313dc6c55f3SJames Feist interfacePair.second.find("AllowedFailures"); 314dc6c55f3SJames Feist if (findCount == interfacePair.second.end()) 315dc6c55f3SJames Feist { 316dc6c55f3SJames Feist std::cerr << "Malformed redundancy record \n"; 317dc6c55f3SJames Feist return; 318dc6c55f3SJames Feist } 319dc6c55f3SJames Feist std::vector<std::string> sensorList; 320dc6c55f3SJames Feist 321dc6c55f3SJames Feist for (const auto& sensor : sensors) 322dc6c55f3SJames Feist { 323dc6c55f3SJames Feist sensorList.push_back( 324dc6c55f3SJames Feist "/xyz/openbmc_project/sensors/fan_tach/" + 325dc6c55f3SJames Feist sensor.second->name); 326dc6c55f3SJames Feist } 327dc6c55f3SJames Feist systemRedundancy = std::make_unique<RedundancySensor>( 328dc6c55f3SJames Feist variant_ns::get<uint64_t>(findCount->second), 329dc6c55f3SJames Feist sensorList, objectServer); 330dc6c55f3SJames Feist 331dc6c55f3SJames Feist return; 332dc6c55f3SJames Feist } 333dc6c55f3SJames Feist } 334dc6c55f3SJames Feist } 335dc6c55f3SJames Feist }, 336dc6c55f3SJames Feist "xyz.openbmc_project.EntityManager", "/", 337dc6c55f3SJames Feist "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 338dc6c55f3SJames Feist } 339dc6c55f3SJames Feist 3406714a25aSJames Feist int main(int argc, char** argv) 3416714a25aSJames Feist { 3426714a25aSJames Feist boost::asio::io_service io; 3436714a25aSJames Feist auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 3446714a25aSJames Feist systemBus->request_name("xyz.openbmc_project.FanSensor"); 3456714a25aSJames Feist sdbusplus::asio::object_server objectServer(systemBus); 3466714a25aSJames Feist boost::container::flat_map<std::string, std::unique_ptr<TachSensor>> 3476714a25aSJames Feist tachSensors; 3486714a25aSJames Feist boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>> 3496714a25aSJames Feist pwmSensors; 3506714a25aSJames Feist std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches; 3516714a25aSJames Feist std::unique_ptr<boost::container::flat_set<std::string>> sensorsChanged = 3526714a25aSJames Feist std::make_unique<boost::container::flat_set<std::string>>(); 3536714a25aSJames Feist 3546714a25aSJames Feist io.post([&]() { 3556714a25aSJames Feist createSensors(io, objectServer, tachSensors, pwmSensors, systemBus, 3566714a25aSJames Feist nullptr); 357dc6c55f3SJames Feist createRedundancySensor(tachSensors, systemBus, objectServer); 3586714a25aSJames Feist }); 3596714a25aSJames Feist 3606714a25aSJames Feist boost::asio::deadline_timer filterTimer(io); 3616714a25aSJames Feist std::function<void(sdbusplus::message::message&)> eventHandler = 3626714a25aSJames Feist [&](sdbusplus::message::message& message) { 3636714a25aSJames Feist if (message.is_method_error()) 3646714a25aSJames Feist { 3656714a25aSJames Feist std::cerr << "callback method error\n"; 3666714a25aSJames Feist return; 3676714a25aSJames Feist } 3686714a25aSJames Feist sensorsChanged->insert(message.get_path()); 3696714a25aSJames Feist // this implicitly cancels the timer 3706714a25aSJames Feist filterTimer.expires_from_now(boost::posix_time::seconds(1)); 3716714a25aSJames Feist 3726714a25aSJames Feist filterTimer.async_wait([&](const boost::system::error_code& ec) { 3736714a25aSJames Feist if (ec == boost::asio::error::operation_aborted) 3746714a25aSJames Feist { 3756714a25aSJames Feist /* we were canceled*/ 3766714a25aSJames Feist return; 3776714a25aSJames Feist } 3786714a25aSJames Feist else if (ec) 3796714a25aSJames Feist { 3806714a25aSJames Feist std::cerr << "timer error\n"; 3816714a25aSJames Feist return; 3826714a25aSJames Feist } 3836714a25aSJames Feist createSensors(io, objectServer, tachSensors, pwmSensors, 3846714a25aSJames Feist systemBus, sensorsChanged); 3856714a25aSJames Feist }); 3866714a25aSJames Feist }; 3876714a25aSJames Feist 3889ced0a38SJae Hyun Yoo for (const char* type : sensorTypes) 3896714a25aSJames Feist { 3906714a25aSJames Feist auto match = std::make_unique<sdbusplus::bus::match::match>( 3916714a25aSJames Feist static_cast<sdbusplus::bus::bus&>(*systemBus), 3926714a25aSJames Feist "type='signal',member='PropertiesChanged',path_namespace='" + 3939ced0a38SJae Hyun Yoo std::string(inventoryPath) + "',arg0namespace='" + type + "'", 3946714a25aSJames Feist eventHandler); 3956714a25aSJames Feist matches.emplace_back(std::move(match)); 3966714a25aSJames Feist } 3976714a25aSJames Feist 398dc6c55f3SJames Feist // redundancy sensor 399dc6c55f3SJames Feist std::function<void(sdbusplus::message::message&)> redundancyHandler = 400dc6c55f3SJames Feist [&tachSensors, &systemBus, 401dc6c55f3SJames Feist &objectServer](sdbusplus::message::message& message) { 402dc6c55f3SJames Feist createRedundancySensor(tachSensors, systemBus, objectServer); 403dc6c55f3SJames Feist }; 404dc6c55f3SJames Feist auto match = std::make_unique<sdbusplus::bus::match::match>( 405dc6c55f3SJames Feist static_cast<sdbusplus::bus::bus&>(*systemBus), 406dc6c55f3SJames Feist "type='signal',member='PropertiesChanged',path_namespace='" + 407dc6c55f3SJames Feist std::string(inventoryPath) + "',arg0namespace='" + 408dc6c55f3SJames Feist redundancyConfiguration + "'", 409dc6c55f3SJames Feist redundancyHandler); 410dc6c55f3SJames Feist matches.emplace_back(std::move(match)); 411dc6c55f3SJames Feist 4126714a25aSJames Feist io.run(); 4136714a25aSJames Feist } 414