1 /* 2 * Copyright 2019 Google Inc. 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 "handler.hpp" 18 19 #include "errors.hpp" 20 21 #include <ipmid/api.h> 22 23 #include <cinttypes> 24 #include <cstdio> 25 #include <filesystem> 26 #include <fstream> 27 #include <map> 28 #include <nlohmann/json.hpp> 29 #include <phosphor-logging/elog-errors.hpp> 30 #include <phosphor-logging/log.hpp> 31 #include <sdbusplus/bus.hpp> 32 #include <sstream> 33 #include <string> 34 #include <tuple> 35 #include <xyz/openbmc_project/Common/error.hpp> 36 37 // The phosphor-host-ipmi daemon requires a configuration that maps 38 // the if_name to the IPMI LAN channel. However, that doesn't strictly 39 // define which is meant to be used for NCSI. 40 #ifndef NCSI_IPMI_CHANNEL 41 #define NCSI_IPMI_CHANNEL 1 42 #endif 43 44 #ifndef NCSI_IF_NAME 45 #define NCSI_IF_NAME eth0 46 #endif 47 48 // To deal with receiving a string without quotes. 49 #define QUOTE(name) #name 50 #define STR(macro) QUOTE(macro) 51 #define NCSI_IF_NAME_STR STR(NCSI_IF_NAME) 52 53 namespace google 54 { 55 namespace ipmi 56 { 57 namespace fs = std::filesystem; 58 using Json = nlohmann::json; 59 using namespace phosphor::logging; 60 using InternalFailure = 61 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 62 63 std::tuple<std::uint8_t, std::string> Handler::getEthDetails() const 64 { 65 return std::make_tuple(NCSI_IPMI_CHANNEL, NCSI_IF_NAME_STR); 66 } 67 68 std::int64_t Handler::getRxPackets(const std::string& name) const 69 { 70 std::ostringstream opath; 71 opath << "/sys/class/net/" << name << "/statistics/rx_packets"; 72 std::string path = opath.str(); 73 74 // Minor sanity & security check (of course, I'm less certain if unicode 75 // comes into play here. 76 // 77 // Basically you can't easily inject ../ or /../ into the path below. 78 if (name.find("/") != std::string::npos) 79 { 80 std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str()); 81 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 82 } 83 84 std::error_code ec; 85 if (!fs::exists(path, ec)) 86 { 87 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str()); 88 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 89 } 90 // We're uninterested in the state of ec. 91 92 int64_t count = 0; 93 std::ifstream ifs; 94 ifs.exceptions(std::ifstream::failbit); 95 try 96 { 97 ifs.open(path); 98 ifs >> count; 99 } 100 catch (std::ios_base::failure& fail) 101 { 102 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 103 } 104 105 return count; 106 } 107 108 VersionTuple Handler::getCpldVersion(unsigned int id) const 109 { 110 std::ostringstream opath; 111 opath << "/run/cpld" << id << ".version"; 112 // Check for file 113 114 std::error_code ec; 115 if (!fs::exists(opath.str(), ec)) 116 { 117 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", 118 opath.str().c_str()); 119 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 120 } 121 // We're uninterested in the state of ec. 122 123 // If file exists, read. 124 std::ifstream ifs; 125 ifs.exceptions(std::ifstream::failbit); 126 std::string value; 127 try 128 { 129 ifs.open(opath.str()); 130 ifs >> value; 131 } 132 catch (std::ios_base::failure& fail) 133 { 134 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 135 } 136 137 // If value parses as expected, return version. 138 VersionTuple version = std::make_tuple(0, 0, 0, 0); 139 140 int num_fields = 141 std::sscanf(value.c_str(), "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8, 142 &std::get<0>(version), &std::get<1>(version), 143 &std::get<2>(version), &std::get<3>(version)); 144 if (num_fields == 0) 145 { 146 std::fprintf(stderr, "Invalid version.\n"); 147 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 148 } 149 150 return version; 151 } 152 153 static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay"; 154 static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 155 static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; 156 static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 157 static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target"; 158 159 void Handler::psuResetDelay(std::uint32_t delay) const 160 { 161 using namespace phosphor::logging; 162 163 std::ofstream ofs; 164 ofs.open(TIME_DELAY_FILENAME, std::ofstream::out); 165 if (!ofs.good()) 166 { 167 std::fprintf(stderr, "Unable to open file for output.\n"); 168 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 169 } 170 171 ofs << "PSU_HARDRESET_DELAY=" << delay << std::endl; 172 if (ofs.fail()) 173 { 174 std::fprintf(stderr, "Write failed\n"); 175 ofs.close(); 176 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 177 } 178 179 // Write succeeded, please continue. 180 ofs.flush(); 181 ofs.close(); 182 183 auto bus = sdbusplus::bus::new_default(); 184 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 185 SYSTEMD_INTERFACE, "StartUnit"); 186 187 method.append(PSU_HARDRESET_TARGET); 188 method.append("replace"); 189 190 try 191 { 192 bus.call_noreply(method); 193 } 194 catch (const sdbusplus::exception::SdBusError& ex) 195 { 196 log<level::ERR>("Failed to call PSU hard reset"); 197 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 198 } 199 } 200 201 static Json config{}; 202 static bool parsed = false; 203 static const std::map<uint8_t, std::string> entityIdToName{ 204 {0x03, "cpu"}, 205 {0x04, "storage_device"}, 206 {0x06, "system_management_module"}, 207 {0x08, "memory_module"}, 208 {0x0B, "add_in_card"}, 209 {0x17, "system_chassis"}, 210 {0x20, "memory_device"}}; 211 static constexpr auto configFile = 212 "/usr/share/ipmi-entity-association/entity_association_map.json"; 213 214 Json parse_config() 215 { 216 std::ifstream jsonFile(configFile); 217 if (!jsonFile.is_open()) 218 { 219 log<level::ERR>("Entity association JSON file not found"); 220 elog<InternalFailure>(); 221 } 222 223 auto data = Json::parse(jsonFile, nullptr, false); 224 if (data.is_discarded()) 225 { 226 log<level::ERR>("Entity association JSON parser failure"); 227 elog<InternalFailure>(); 228 } 229 230 return data; 231 } 232 233 std::string read(const std::string& type, uint8_t instance, const Json& config) 234 { 235 static const std::vector<Json> empty{}; 236 std::vector<Json> readings = config.value(type, empty); 237 std::string name = ""; 238 for (const auto& j : readings) 239 { 240 uint8_t instanceNum = j.value("instance", 0); 241 // Not the instance we're interested in 242 if (instanceNum != instance) 243 { 244 continue; 245 } 246 247 // Found the instance we're interested in 248 name = j.value("name", ""); 249 250 break; 251 } 252 return name; 253 } 254 255 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance) 256 { 257 // Check if we support this Entity ID. 258 auto it = entityIdToName.find(id); 259 if (it == entityIdToName.end()) 260 { 261 log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id)); 262 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 263 } 264 265 std::string entityName; 266 try 267 { 268 // Parse the JSON config file. 269 if (!parsed) 270 { 271 config = parse_config(); 272 parsed = true; 273 } 274 275 // Find the "entity id:entity instance" mapping to entity name. 276 entityName = read(it->second, instance, config); 277 if (entityName.empty()) 278 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 279 } 280 catch (InternalFailure& e) 281 { 282 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 283 } 284 285 return entityName; 286 } 287 288 Handler handlerImpl; 289 290 } // namespace ipmi 291 } // namespace google 292