log_services: Add download of log entries
Add a GET function for endpoint
/redfish/v1/Systems/system/LogServices/EventLog/attachment/<str>
which would read the File Path property of the specified entry,
encode it as base64, and send it off. This allows the user to
offload error logs for analysis and further parsing if needed.
An http header of "Accept: application/octet-stream" or the default "*/*"
is expected.
Tested:
- Ran Redfish validator.
- Verified the contents of the offloaded file were the same as
the file in the BMC, after decoding it with a base64 decoder.
curl -k -H "X-Auth-Token: $token" \
https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/attachment/2
- Verified the supported Accept header values, ex:
"Accept: application/octet-stream;q=1"
"Accept: text/plain;q=0,application/octet-stream;q=1,multipart/form-data;q=2"
- Verified an unsupported Accept header returned "Bad Request", ex:
curl -k -H "X-Auth-Token: $token" -H "Accept: application/*"
curl -k -H "X-Auth-Token: $token" -H "Accept: foo, not/supported"
- Verified 404 was returned for a not found endpoint:
$ curl -k -H "X-Auth-Token: $token" https://${bmc}/redfish/v1/Systems/system/LogServices/EventLog/attachment/foo
{
"error": {
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The requested resource of type EventLogAttachment named foo was not found.",
"MessageArgs": [
"EventLogAttachment",
"foo"
],
"MessageId": "Base.1.8.1.ResourceNotFound",
"MessageSeverity": "Critical",
"Resolution": "Provide a valid resource identifier and resubmit the request."
}
],
"code": "Base.1.8.1.ResourceNotFound",
"message": "The requested resource of type EventLogAttachment named foo was not found."
}
}
Change-Id: Id9e2308ebedc70852a2ed62def107648f7e6fb7a
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index a9832f1..9c76c5d 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -171,6 +171,7 @@
nodes.emplace_back(std::make_unique<DBusLogServiceActionsClear>(app));
nodes.emplace_back(std::make_unique<DBusEventLogEntryCollection>(app));
nodes.emplace_back(std::make_unique<DBusEventLogEntry>(app));
+ nodes.emplace_back(std::make_unique<DBusEventLogEntryDownload>(app));
#endif
nodes.emplace_back(
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index 1d076a3..2a52830 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -22,9 +22,12 @@
#include "task.hpp"
#include <systemd/sd-journal.h>
+#include <unistd.h>
+#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/beast/core/span.hpp>
+#include <boost/beast/http.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/system/linux_error.hpp>
#include <error_messages.hpp>
@@ -1757,6 +1760,131 @@
}
};
+class DBusEventLogEntryDownload : public Node
+{
+ public:
+ DBusEventLogEntryDownload(App& app) :
+ Node(
+ app,
+ "/redfish/v1/Systems/system/LogServices/EventLog/attachment/<str>/",
+ std::string())
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::put, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
+ {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
+ }
+
+ private:
+ void doGet(crow::Response& res, const crow::Request& req,
+ const std::vector<std::string>& params) override
+ {
+ std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
+ if (params.size() != 1)
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ std::string_view acceptHeader = req.getHeaderValue("Accept");
+ // The iterators in boost/http/rfc7230.hpp end the string if '/' is
+ // found, so replace it with arbitrary character '|' which is not part
+ // of the Accept header syntax.
+ std::string acceptStr =
+ boost::replace_all_copy(std::string(acceptHeader), "/", "|");
+ boost::beast::http::ext_list acceptTypes{acceptStr};
+ bool supported = false;
+ for (const auto& type : acceptTypes)
+ {
+ if ((type.first == "*|*") ||
+ (type.first == "application|octet-stream"))
+ {
+ supported = true;
+ break;
+ }
+ }
+ if (!supported)
+ {
+ asyncResp->res.result(boost::beast::http::status::bad_request);
+ return;
+ }
+
+ std::string entryID = params[0];
+ dbus::utility::escapePathForDbus(entryID);
+
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, entryID](const boost::system::error_code ec,
+ const sdbusplus::message::unix_fd& unixfd) {
+ if (ec.value() == EBADR)
+ {
+ messages::resourceNotFound(asyncResp->res,
+ "EventLogAttachment", entryID);
+ return;
+ }
+ if (ec)
+ {
+ BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ int fd = -1;
+ fd = dup(unixfd);
+ if (fd == -1)
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ long long int size = lseek(fd, 0, SEEK_END);
+ if (size == -1)
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ // Arbitrary max size of 64kb
+ constexpr int maxFileSize = 65536;
+ if (size > maxFileSize)
+ {
+ BMCWEB_LOG_ERROR
+ << "File size exceeds maximum allowed size of "
+ << maxFileSize;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ std::vector<char> data(static_cast<size_t>(size));
+ long long int rc = lseek(fd, 0, SEEK_SET);
+ if (rc == -1)
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ rc = read(fd, data.data(), data.size());
+ if ((rc == -1) || (rc != size))
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ close(fd);
+
+ std::string_view strData(data.data(), data.size());
+ std::string output = crow::utility::base64encode(strData);
+
+ asyncResp->res.addHeader("Content-Type",
+ "application/octet-stream");
+ asyncResp->res.addHeader("Content-Transfer-Encoding", "Base64");
+ asyncResp->res.body() = std::move(output);
+ },
+ "xyz.openbmc_project.Logging",
+ "/xyz/openbmc_project/logging/entry/" + entryID,
+ "xyz.openbmc_project.Logging.Entry", "GetEntry");
+ }
+};
+
class BMCLogServiceCollection : public Node
{
public: