LogService: Retrieve dump generated by Manager

Adds support for retrieving the dump file that's generated by
phosphor-debug-collector as a result of using the LogServices/Dump
Action LogService.CollectDiagnosticData from the bmc Manager resource.

Refactors the handling for
/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment
to use one of the new functions and remove the large lambda.

Tested:
I began the dump generation process by sending a POST request to
/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData.
That spawned a Task to track the dump being generated by
phosphor-debug-collector.  The dump was retrieved by querying the
/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/attachment URI
which is associated with the Task.

Verified that an event log returned by querying
/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment
is the same as it was before this change.

Signed-off-by: Carson Labrado <clabrado@google.com>
Change-Id: I352b2628a9990bbde40f22e6134f02c89189c925
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index 6594228..2899b01 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -701,6 +701,160 @@
         "xyz.openbmc_project.Object.Delete", "Delete");
 }
 
+inline void
+    downloadEntryCallback(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                          const std::string& entryID,
+                          const std::string& downloadEntryType,
+                          const boost::system::error_code& ec,
+                          const sdbusplus::message::unix_fd& unixfd)
+{
+    if (ec.value() == EBADR)
+    {
+        messages::resourceNotFound(asyncResp->res, "EntryAttachment", entryID);
+        return;
+    }
+    if (ec)
+    {
+        BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    // Make sure we know how to process the retrieved entry attachment
+    if ((downloadEntryType != "BMC") && (downloadEntryType != "System"))
+    {
+        BMCWEB_LOG_ERROR("downloadEntryCallback() invalid entry type: {}",
+                         downloadEntryType);
+        messages::internalError(asyncResp->res);
+    }
+
+    int fd = -1;
+    fd = dup(unixfd);
+    if (fd < 0)
+    {
+        BMCWEB_LOG_ERROR("Failed to open file");
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    long long int size = lseek(fd, 0, SEEK_END);
+    if (size <= 0)
+    {
+        BMCWEB_LOG_ERROR("Failed to get size of file, lseek() returned {}",
+                         size);
+        messages::internalError(asyncResp->res);
+        close(fd);
+        return;
+    }
+
+    // Arbitrary max size of 20MB to accommodate BMC dumps
+    constexpr int maxFileSize = 20 * 1024 * 1024;
+    if (size > maxFileSize)
+    {
+        BMCWEB_LOG_ERROR("File size {} exceeds maximum allowed size of {}",
+                         size, maxFileSize);
+        messages::internalError(asyncResp->res);
+        close(fd);
+        return;
+    }
+    long long int rc = lseek(fd, 0, SEEK_SET);
+    if (rc < 0)
+    {
+        BMCWEB_LOG_ERROR("Failed to reset file offset to 0");
+        messages::internalError(asyncResp->res);
+        close(fd);
+        return;
+    }
+
+    asyncResp->res.body().resize(static_cast<size_t>(size), '\0');
+    rc = read(fd, asyncResp->res.body().data(), asyncResp->res.body().size());
+    if ((rc == -1) || (rc != size))
+    {
+        BMCWEB_LOG_ERROR("Failed to read in file");
+        messages::internalError(asyncResp->res);
+        close(fd);
+        return;
+    }
+    close(fd);
+
+    if (downloadEntryType == "System")
+    {
+        // We need to encode the data.  Doing it this way saves the other paths
+        // from having to make a copy into a temporary variable.
+        asyncResp->res.body() =
+            crow::utility::base64encode(asyncResp->res.body());
+        asyncResp->res.addHeader(
+            boost::beast::http::field::content_transfer_encoding, "Base64");
+    }
+
+    asyncResp->res.addHeader(boost::beast::http::field::content_type,
+                             "application/octet-stream");
+}
+
+inline void
+    downloadDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                      const std::string& entryID, const std::string& dumpType)
+{
+    if (dumpType != "BMC")
+    {
+        BMCWEB_LOG_WARNING("Can't find Dump Entry {}", entryID);
+        messages::resourceNotFound(asyncResp->res, dumpType + " dump", entryID);
+        return;
+    }
+
+    std::string dumpEntryPath =
+        sdbusplus::message::object_path("/xyz/openbmc_project/dump/") /
+        std::string(boost::algorithm::to_lower_copy(dumpType)) / "entry" /
+        entryID;
+
+    auto downloadDumpEntryHandler =
+        [asyncResp, entryID,
+         dumpType](const boost::system::error_code& ec,
+                   const sdbusplus::message::unix_fd& unixfd) {
+        downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd);
+    };
+
+    crow::connections::systemBus->async_method_call(
+        std::move(downloadDumpEntryHandler), "xyz.openbmc_project.Dump.Manager",
+        dumpEntryPath, "xyz.openbmc_project.Dump.Entry", "GetFileHandle");
+}
+
+inline void
+    downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                          const std::string& systemName,
+                          const std::string& entryID,
+                          const std::string& dumpType)
+{
+    if constexpr (bmcwebEnableMultiHost)
+    {
+        // Option currently returns no systems.  TBD
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+    if (systemName != "system")
+    {
+        messages::resourceNotFound(asyncResp->res, "ComputerSystem",
+                                   systemName);
+        return;
+    }
+
+    std::string entryPath =
+        sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") /
+        entryID;
+
+    auto downloadEventLogEntryHandler =
+        [asyncResp, entryID,
+         dumpType](const boost::system::error_code& ec,
+                   const sdbusplus::message::unix_fd& unixfd) {
+        downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd);
+    };
+
+    crow::connections::systemBus->async_method_call(
+        std::move(downloadEventLogEntryHandler), "xyz.openbmc_project.Logging",
+        entryPath, "xyz.openbmc_project.Logging.Entry", "GetEntry");
+}
+
 inline DumpCreationProgress
     mapDbusStatusToDumpProgress(const std::string& status)
 {
@@ -1960,114 +2114,6 @@
         });
 }
 
-inline void requestRoutesDBusEventLogEntryDownload(App& app)
-{
-    BMCWEB_ROUTE(
-        app,
-        "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment")
-        .privileges(redfish::privileges::getLogEntry)
-        .methods(boost::beast::http::verb::get)(
-            [&app](const crow::Request& req,
-                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-                   const std::string& systemName, const std::string& param) {
-        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-        {
-            return;
-        }
-        if (!http_helpers::isContentTypeAllowed(
-                req.getHeaderValue("Accept"),
-                http_helpers::ContentType::OctetStream, true))
-        {
-            asyncResp->res.result(boost::beast::http::status::bad_request);
-            return;
-        }
-        if constexpr (bmcwebEnableMultiHost)
-        {
-            // Option currently returns no systems.  TBD
-            messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                       systemName);
-            return;
-        }
-        if (systemName != "system")
-        {
-            messages::resourceNotFound(asyncResp->res, "ComputerSystem",
-                                       systemName);
-            return;
-        }
-
-        std::string entryID = param;
-        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(boost::beast::http::field::content_type,
-                                     "application/octet-stream");
-            asyncResp->res.addHeader(
-                boost::beast::http::field::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");
-        });
-}
-
 constexpr const char* hostLoggerFolderPath = "/var/log/console";
 
 inline bool
@@ -2829,6 +2875,7 @@
     }
     getDumpEntryById(asyncResp, dumpId, dumpType);
 }
+
 inline void handleLogServicesDumpEntryComputerSystemGet(
     crow::App& app, const crow::Request& req,
     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
@@ -2875,6 +2922,37 @@
     deleteDumpEntry(asyncResp, dumpId, "System");
 }
 
+inline void handleLogServicesDumpEntryDownloadGet(
+    crow::App& app, const std::string& dumpType, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& dumpId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+    downloadDumpEntry(asyncResp, dumpId, dumpType);
+}
+
+inline void handleDBusEventLogEntryDownloadGet(
+    crow::App& app, const std::string& dumpType, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& systemName, const std::string& entryID)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+    if (!http_helpers::isContentTypeAllowed(
+            req.getHeaderValue("Accept"),
+            http_helpers::ContentType::OctetStream, true))
+    {
+        asyncResp->res.result(boost::beast::http::status::bad_request);
+        return;
+    }
+    downloadEventLogEntry(asyncResp, systemName, entryID, dumpType);
+}
+
 inline void handleLogServicesDumpCollectDiagnosticDataPost(
     crow::App& app, const std::string& dumpType, const crow::Request& req,
     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
@@ -2979,6 +3057,16 @@
             handleLogServicesDumpEntryDelete, std::ref(app), "BMC"));
 }
 
+inline void requestRoutesBMCDumpEntryDownload(App& app)
+{
+    BMCWEB_ROUTE(
+        app,
+        "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/attachment")
+        .privileges(redfish::privileges::getLogEntry)
+        .methods(boost::beast::http::verb::get)(std::bind_front(
+            handleLogServicesDumpEntryDownloadGet, std::ref(app), "BMC"));
+}
+
 inline void requestRoutesBMCDumpCreate(App& app)
 {
     BMCWEB_ROUTE(
@@ -3000,6 +3088,16 @@
             handleLogServicesDumpClearLogPost, std::ref(app), "BMC"));
 }
 
+inline void requestRoutesDBusEventLogEntryDownload(App& app)
+{
+    BMCWEB_ROUTE(
+        app,
+        "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment")
+        .privileges(redfish::privileges::getLogEntry)
+        .methods(boost::beast::http::verb::get)(std::bind_front(
+            handleDBusEventLogEntryDownloadGet, std::ref(app), "System"));
+}
+
 inline void requestRoutesFaultLogDumpService(App& app)
 {
     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/")