1 /*
2 // Copyright (c) 2018 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 #pragma once
17 
18 #include "node.hpp"
19 
20 #include <boost/container/flat_map.hpp>
21 #include <experimental/filesystem>
22 
23 namespace redfish
24 {
25 
26 constexpr char const *CPU_LOG_OBJECT = "com.intel.CpuDebugLog";
27 constexpr char const *CPU_LOG_PATH = "/com/intel/CpuDebugLog";
28 constexpr char const *CPU_LOG_IMMEDIATE_PATH =
29     "/com/intel/CpuDebugLog/Immediate";
30 constexpr char const *CPU_LOG_INTERFACE = "com.intel.CpuDebugLog";
31 constexpr char const *CPU_LOG_IMMEDIATE_INTERFACE =
32     "com.intel.CpuDebugLog.Immediate";
33 constexpr char const *CPU_LOG_RAW_PECI_INTERFACE =
34     "com.intel.CpuDebugLog.SendRawPeci";
35 
36 namespace fs = std::experimental::filesystem;
37 
38 class LogServiceCollection : public Node
39 {
40   public:
41     template <typename CrowApp>
42     LogServiceCollection(CrowApp &app) :
43         Node(app, "/redfish/v1/Managers/openbmc/LogServices/")
44     {
45         // Collections use static ID for SubRoute to add to its parent, but only
46         // load dynamic data so the duplicate static members don't get displayed
47         Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/LogServices";
48         entityPrivileges = {
49             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
50             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
51             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
52             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
53             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
54             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
55     }
56 
57   private:
58     /**
59      * Functions triggers appropriate requests on DBus
60      */
61     void doGet(crow::Response &res, const crow::Request &req,
62                const std::vector<std::string> &params) override
63     {
64         // Collections don't include the static data added by SubRoute because
65         // it has a duplicate entry for members
66         res.jsonValue["@odata.type"] =
67             "#LogServiceCollection.LogServiceCollection";
68         res.jsonValue["@odata.context"] =
69             "/redfish/v1/"
70             "$metadata#LogServiceCollection.LogServiceCollection";
71         res.jsonValue["@odata.id"] = "/redfish/v1/Managers/openbmc/LogServices";
72         res.jsonValue["Name"] = "Open BMC Log Services Collection";
73         res.jsonValue["Description"] =
74             "Collection of LogServices for this Manager";
75         nlohmann::json &logserviceArray = res.jsonValue["Members"];
76         logserviceArray = nlohmann::json::array();
77 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
78         logserviceArray.push_back(
79             {{"@odata.id", "/redfish/v1/Managers/openbmc/LogServices/CpuLog"}});
80 #endif
81         res.jsonValue["Members@odata.count"] = logserviceArray.size();
82         res.end();
83     }
84 };
85 
86 class CpuLogService : public Node
87 {
88   public:
89     template <typename CrowApp>
90     CpuLogService(CrowApp &app) :
91         Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog")
92     {
93         // Set the id for SubRoute
94         Node::json["@odata.id"] =
95             "/redfish/v1/Managers/openbmc/LogServices/CpuLog";
96         entityPrivileges = {
97             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
98             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
99             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
100             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
101             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
102             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
103     }
104 
105   private:
106     /**
107      * Functions triggers appropriate requests on DBus
108      */
109     void doGet(crow::Response &res, const crow::Request &req,
110                const std::vector<std::string> &params) override
111     {
112         // Copy over the static data to include the entries added by SubRoute
113         res.jsonValue = Node::json;
114         res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
115         res.jsonValue["@odata.context"] = "/redfish/v1/"
116                                           "$metadata#LogService.LogService";
117         res.jsonValue["Name"] = "Open BMC CPU Log Service";
118         res.jsonValue["Description"] = "CPU Log Service";
119         res.jsonValue["Id"] = "CPU Log";
120         res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
121         res.jsonValue["MaxNumberOfRecords"] = 3;
122         res.jsonValue["Actions"] = {
123             {"Oem",
124              {{"#CpuLog.Immediate",
125                {{"target",
126                  "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
127                  "CpuLog.Immediate"}}}}}};
128 
129 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
130         res.jsonValue["Actions"]["Oem"].push_back(
131             {"#CpuLog.SendRawPeci",
132              {{"target",
133                "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
134                "CpuLog.SendRawPeci"}}});
135 #endif
136         res.end();
137     }
138 };
139 
140 class CpuLogEntryCollection : public Node
141 {
142   public:
143     template <typename CrowApp>
144     CpuLogEntryCollection(CrowApp &app) :
145         Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries")
146     {
147         // Collections use static ID for SubRoute to add to its parent, but only
148         // load dynamic data so the duplicate static members don't get displayed
149         Node::json["@odata.id"] =
150             "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries";
151         entityPrivileges = {
152             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
153             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
154             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
155             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
156             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
157             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
158     }
159 
160   private:
161     /**
162      * Functions triggers appropriate requests on DBus
163      */
164     void doGet(crow::Response &res, const crow::Request &req,
165                const std::vector<std::string> &params) override
166     {
167         // Collections don't include the static data added by SubRoute because
168         // it has a duplicate entry for members
169         auto getLogEntriesCallback =
170             [&res](const boost::system::error_code ec,
171                    const std::vector<std::string> &resp) {
172                 if (ec)
173                 {
174                     if (ec.value() !=
175                         boost::system::errc::no_such_file_or_directory)
176                     {
177                         BMCWEB_LOG_DEBUG << "failed to get entries ec: "
178                                          << ec.message();
179                         res.result(
180                             boost::beast::http::status::internal_server_error);
181                         res.end();
182                         return;
183                     }
184                 }
185                 res.jsonValue["@odata.type"] =
186                     "#LogEntryCollection.LogEntryCollection";
187                 res.jsonValue["@odata.context"] =
188                     "/redfish/v1/"
189                     "$metadata#LogEntryCollection.LogEntryCollection";
190                 res.jsonValue["Name"] = "Open BMC CPU Log Entries";
191                 res.jsonValue["Description"] = "Collection of CPU Log Entries";
192                 nlohmann::json &logentry_array = res.jsonValue["Members"];
193                 logentry_array = nlohmann::json::array();
194                 for (const std::string &objpath : resp)
195                 {
196                     // Don't list the immediate log
197                     if (objpath.compare(CPU_LOG_IMMEDIATE_PATH) == 0)
198                     {
199                         continue;
200                     }
201                     std::size_t last_pos = objpath.rfind("/");
202                     if (last_pos != std::string::npos)
203                     {
204                         logentry_array.push_back(
205                             {{"@odata.id", "/redfish/v1/Managers/openbmc/"
206                                            "LogServices/CpuLog/Entries/" +
207                                                objpath.substr(last_pos + 1)}});
208                     }
209                 }
210                 res.jsonValue["Members@odata.count"] = logentry_array.size();
211                 res.end();
212             };
213         crow::connections::systemBus->async_method_call(
214             std::move(getLogEntriesCallback),
215             "xyz.openbmc_project.ObjectMapper",
216             "/xyz/openbmc_project/object_mapper",
217             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
218             std::array<const char *, 1>{CPU_LOG_INTERFACE});
219     }
220 };
221 
222 std::string getLogCreatedTime(const nlohmann::json &cpuLog)
223 {
224     nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
225     if (metaIt != cpuLog.end())
226     {
227         nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
228         if (tsIt != metaIt->end())
229         {
230             const std::string *logTime = tsIt->get_ptr<const std::string *>();
231             if (logTime != nullptr)
232             {
233                 return *logTime;
234             }
235         }
236     }
237     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
238 
239     return std::string();
240 }
241 
242 class CpuLogEntry : public Node
243 {
244   public:
245     CpuLogEntry(CrowApp &app) :
246         Node(app,
247              "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries/<str>/",
248              std::string())
249     {
250         entityPrivileges = {
251             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
252             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
253             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
254             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
255             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
256             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
257     }
258 
259   private:
260     void doGet(crow::Response &res, const crow::Request &req,
261                const std::vector<std::string> &params) override
262     {
263         if (params.size() != 1)
264         {
265             res.result(boost::beast::http::status::internal_server_error);
266             res.end();
267             return;
268         }
269         const uint8_t log_id = std::atoi(params[0].c_str());
270         auto getStoredLogCallback = [&res,
271                                      log_id](const boost::system::error_code ec,
272                                              const sdbusplus::message::variant<
273                                                  std::string> &resp) {
274             if (ec)
275             {
276                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
277                 res.result(boost::beast::http::status::internal_server_error);
278                 res.end();
279                 return;
280             }
281             const std::string *log = mapbox::getPtr<const std::string>(resp);
282             if (log == nullptr)
283             {
284                 res.result(boost::beast::http::status::internal_server_error);
285                 res.end();
286                 return;
287             }
288             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
289             if (j.is_discarded())
290             {
291                 res.result(boost::beast::http::status::internal_server_error);
292                 res.end();
293                 return;
294             }
295             std::string t = getLogCreatedTime(j);
296             res.jsonValue = {
297                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
298                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
299                 {"@odata.id",
300                  "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Entries/" +
301                      std::to_string(log_id)},
302                 {"Name", "CPU Debug Log"},
303                 {"Id", log_id},
304                 {"EntryType", "Oem"},
305                 {"OemRecordFormat", "Intel CPU Log"},
306                 {"Oem", {{"Intel", std::move(j)}}},
307                 {"Created", std::move(t)}};
308             res.end();
309         };
310         crow::connections::systemBus->async_method_call(
311             std::move(getStoredLogCallback), CPU_LOG_OBJECT,
312             CPU_LOG_PATH + std::string("/") + std::to_string(log_id),
313             "org.freedesktop.DBus.Properties", "Get", CPU_LOG_INTERFACE, "Log");
314     }
315 };
316 
317 class ImmediateCpuLog : public Node
318 {
319   public:
320     ImmediateCpuLog(CrowApp &app) :
321         Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
322                   "CpuLog.Immediate")
323     {
324         entityPrivileges = {
325             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
326             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
327             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
328             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
329             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
330             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
331     }
332 
333   private:
334     void doPost(crow::Response &res, const crow::Request &req,
335                 const std::vector<std::string> &params) override
336     {
337         static std::unique_ptr<sdbusplus::bus::match::match>
338             immediateLogMatcher;
339 
340         // Only allow one Immediate Log request at a time
341         if (immediateLogMatcher != nullptr)
342         {
343             res.addHeader("Retry-After", "30");
344             res.result(boost::beast::http::status::service_unavailable);
345             messages::addMessageToJson(
346                 res.jsonValue, messages::serviceTemporarilyUnavailable("30"),
347                 "/CpuLog.Immediate");
348             res.end();
349             return;
350         }
351         // Make this static so it survives outside this method
352         static boost::asio::deadline_timer timeout(*req.ioService);
353 
354         timeout.expires_from_now(boost::posix_time::seconds(30));
355         timeout.async_wait([&res](const boost::system::error_code &ec) {
356             immediateLogMatcher = nullptr;
357             if (ec)
358             {
359                 // operation_aborted is expected if timer is canceled before
360                 // completion.
361                 if (ec != boost::asio::error::operation_aborted)
362                 {
363                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
364                 }
365                 return;
366             }
367             BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
368 
369             res.result(boost::beast::http::status::internal_server_error);
370             res.end();
371         });
372 
373         auto immediateLogMatcherCallback = [&res](
374                                                sdbusplus::message::message &m) {
375             BMCWEB_LOG_DEBUG << "Immediate log available match fired";
376             boost::system::error_code ec;
377             timeout.cancel(ec);
378             if (ec)
379             {
380                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
381             }
382             sdbusplus::message::object_path obj_path;
383             boost::container::flat_map<
384                 std::string,
385                 boost::container::flat_map<
386                     std::string, sdbusplus::message::variant<std::string>>>
387                 interfaces_added;
388             m.read(obj_path, interfaces_added);
389             const std::string *log = mapbox::getPtr<const std::string>(
390                 interfaces_added[CPU_LOG_INTERFACE]["Log"]);
391             if (log == nullptr)
392             {
393                 res.result(boost::beast::http::status::internal_server_error);
394                 res.end();
395                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
396                 // match object inside which this lambda is executing.  Once it
397                 // is set to nullptr, the match object will be destroyed and the
398                 // lambda will lose its context, including res, so it needs to
399                 // be the last thing done.
400                 immediateLogMatcher = nullptr;
401                 return;
402             }
403             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
404             if (j.is_discarded())
405             {
406                 res.result(boost::beast::http::status::internal_server_error);
407                 res.end();
408                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
409                 // match object inside which this lambda is executing.  Once it
410                 // is set to nullptr, the match object will be destroyed and the
411                 // lambda will lose its context, including res, so it needs to
412                 // be the last thing done.
413                 immediateLogMatcher = nullptr;
414                 return;
415             }
416             std::string t = getLogCreatedTime(j);
417             res.jsonValue = {
418                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
419                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
420                 {"Name", "CPU Debug Log"},
421                 {"EntryType", "Oem"},
422                 {"OemRecordFormat", "Intel CPU Log"},
423                 {"Oem", {{"Intel", std::move(j)}}},
424                 {"Created", std::move(t)}};
425             res.end();
426             // Careful with immediateLogMatcher.  It is a unique_ptr to the
427             // match object inside which this lambda is executing.  Once it is
428             // set to nullptr, the match object will be destroyed and the lambda
429             // will lose its context, including res, so it needs to be the last
430             // thing done.
431             immediateLogMatcher = nullptr;
432         };
433         immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
434             *crow::connections::systemBus,
435             sdbusplus::bus::match::rules::interfacesAdded() +
436                 sdbusplus::bus::match::rules::argNpath(0,
437                                                        CPU_LOG_IMMEDIATE_PATH),
438             std::move(immediateLogMatcherCallback));
439 
440         auto generateImmediateLogCallback =
441             [&res](const boost::system::error_code ec,
442                    const std::string &resp) {
443                 if (ec)
444                 {
445                     if (ec.value() ==
446                         boost::system::errc::operation_not_supported)
447                     {
448                         messages::addMessageToJson(
449                             res.jsonValue, messages::resourceInStandby(),
450                             "/CpuLog.Immediate");
451                         res.result(
452                             boost::beast::http::status::service_unavailable);
453                     }
454                     else
455                     {
456                         res.result(
457                             boost::beast::http::status::internal_server_error);
458                     }
459                     res.end();
460                     boost::system::error_code timeoutec;
461                     timeout.cancel(timeoutec);
462                     if (timeoutec)
463                     {
464                         BMCWEB_LOG_ERROR << "error canceling timer "
465                                          << timeoutec;
466                     }
467                     immediateLogMatcher = nullptr;
468                     return;
469                 }
470             };
471         crow::connections::systemBus->async_method_call(
472             std::move(generateImmediateLogCallback), CPU_LOG_OBJECT,
473             CPU_LOG_PATH, CPU_LOG_IMMEDIATE_INTERFACE, "GenerateImmediateLog");
474     }
475 };
476 
477 class SendRawPeci : public Node
478 {
479   public:
480     SendRawPeci(CrowApp &app) :
481         Node(app, "/redfish/v1/Managers/openbmc/LogServices/CpuLog/Actions/Oem/"
482                   "CpuLog.SendRawPeci")
483     {
484         entityPrivileges = {
485             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
486             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
487             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
488             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
489             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
490             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
491     }
492 
493   private:
494     void doPost(crow::Response &res, const crow::Request &req,
495                 const std::vector<std::string> &params) override
496     {
497         // Get the Raw PECI command from the request
498         nlohmann::json rawPeciCmd;
499         if (!json_util::processJsonFromRequest(res, req, rawPeciCmd))
500         {
501             return;
502         }
503         // Get the Client Address from the request
504         nlohmann::json::const_iterator caIt = rawPeciCmd.find("ClientAddress");
505         if (caIt == rawPeciCmd.end())
506         {
507             messages::addMessageToJson(
508                 res.jsonValue, messages::propertyMissing("ClientAddress"),
509                 "/ClientAddress");
510             res.result(boost::beast::http::status::bad_request);
511             res.end();
512             return;
513         }
514         const uint64_t *ca = caIt->get_ptr<const uint64_t *>();
515         if (ca == nullptr)
516         {
517             messages::addMessageToJson(
518                 res.jsonValue,
519                 messages::propertyValueTypeError(caIt->dump(), "ClientAddress"),
520                 "/ClientAddress");
521             res.result(boost::beast::http::status::bad_request);
522             res.end();
523             return;
524         }
525         // Get the Read Length from the request
526         const uint8_t clientAddress = static_cast<uint8_t>(*ca);
527         nlohmann::json::const_iterator rlIt = rawPeciCmd.find("ReadLength");
528         if (rlIt == rawPeciCmd.end())
529         {
530             messages::addMessageToJson(res.jsonValue,
531                                        messages::propertyMissing("ReadLength"),
532                                        "/ReadLength");
533             res.result(boost::beast::http::status::bad_request);
534             res.end();
535             return;
536         }
537         const uint64_t *rl = rlIt->get_ptr<const uint64_t *>();
538         if (rl == nullptr)
539         {
540             messages::addMessageToJson(
541                 res.jsonValue,
542                 messages::propertyValueTypeError(rlIt->dump(), "ReadLength"),
543                 "/ReadLength");
544             res.result(boost::beast::http::status::bad_request);
545             res.end();
546             return;
547         }
548         // Get the PECI Command from the request
549         const uint32_t readLength = static_cast<uint32_t>(*rl);
550         nlohmann::json::const_iterator pcIt = rawPeciCmd.find("PECICommand");
551         if (pcIt == rawPeciCmd.end())
552         {
553             messages::addMessageToJson(res.jsonValue,
554                                        messages::propertyMissing("PECICommand"),
555                                        "/PECICommand");
556             res.result(boost::beast::http::status::bad_request);
557             res.end();
558             return;
559         }
560         std::vector<uint8_t> peciCommand;
561         for (auto pc : *pcIt)
562         {
563             const uint64_t *val = pc.get_ptr<const uint64_t *>();
564             if (val == nullptr)
565             {
566                 messages::addMessageToJson(
567                     res.jsonValue,
568                     messages::propertyValueTypeError(
569                         pc.dump(),
570                         "PECICommand/" + std::to_string(peciCommand.size())),
571                     "/PECICommand");
572                 res.result(boost::beast::http::status::bad_request);
573                 res.end();
574                 return;
575             }
576             peciCommand.push_back(static_cast<uint8_t>(*val));
577         }
578         // Callback to return the Raw PECI response
579         auto sendRawPeciCallback = [&res](const boost::system::error_code ec,
580                                           const std::vector<uint8_t> &resp) {
581             if (ec)
582             {
583                 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
584                                  << ec.message();
585                 res.result(boost::beast::http::status::internal_server_error);
586                 res.end();
587                 return;
588             }
589             res.jsonValue = {{"Name", "PECI Command Response"},
590                              {"PECIResponse", resp}};
591             res.end();
592         };
593         // Call the SendRawPECI command with the provided data
594         crow::connections::systemBus->async_method_call(
595             std::move(sendRawPeciCallback), CPU_LOG_OBJECT, CPU_LOG_PATH,
596             CPU_LOG_RAW_PECI_INTERFACE, "SendRawPeci", clientAddress,
597             readLength, peciCommand);
598     }
599 };
600 
601 } // namespace redfish
602