Activation: store PSU image in persistent storage

When an activation succeeds, store the PSU image into persistent
storage, which will be used in future in case a PSU is replaced, and the
BMC will need to update the replaced PSU's firmware.
Only the latest image is saved, and old ones are removed for each model.

Tested: On witherspoon, verify the PSU image is saved in persistent
        storage after a successful activation with dummy service, and
        the FilePath inteface is updated with the stored path.
        And after another successful activation, the new image is saved
        and the old one is removed in persistent storage.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I11f3d4a91d045d2316242d8eef968f05250d862e
diff --git a/meson.build b/meson.build
index 9e5b3b3..abf2ddf 100644
--- a/meson.build
+++ b/meson.build
@@ -40,6 +40,8 @@
 cdata.set_quoted('PSU_VERSION_UTIL', get_option('PSU_VERSION_UTIL'))
 cdata.set_quoted('PSU_UPDATE_SERVICE', get_option('PSU_UPDATE_SERVICE'))
 cdata.set_quoted('IMG_DIR', get_option('IMG_DIR'))
+cdata.set_quoted('IMG_DIR_PERSIST', get_option('IMG_DIR_PERSIST'))
+cdata.set_quoted('IMG_DIR_BUILTIN', get_option('IMG_DIR_BUILTIN'))
 
 phosphor_dbus_interfaces = dependency('phosphor-dbus-interfaces')
 phosphor_logging = dependency('phosphor-logging')
diff --git a/meson_options.txt b/meson_options.txt
index 1aee013..4e61922 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -38,3 +38,13 @@
        type: 'string',
        value: 'psu-update@.service',
        description: 'The PSU update service')
+
+option('IMG_DIR_PERSIST',
+       type: 'string',
+       value: '/var/lib/obmc/psu',
+       description: 'The writable directory to store updated PSU images persistently')
+
+option('IMG_DIR_BUILTIN',
+       type: 'string',
+       value: '/usr/local/obmc/psu',
+       description: 'The read-only directory where the built-in PSU images are stored')
diff --git a/src/activation.cpp b/src/activation.cpp
index 2ca7d86..4d190f9 100644
--- a/src/activation.cpp
+++ b/src/activation.cpp
@@ -221,6 +221,7 @@
 
 void Activation::finishActivation()
 {
+    storeImage();
     activationProgress->progress(100);
 
     // TODO: delete the old software object
@@ -293,6 +294,27 @@
     return true;
 }
 
+void Activation::storeImage()
+{
+    // Store image in persistent dir separated by model
+    // and only store the latest one by removing old ones
+    auto src = fs::path(IMG_DIR) / versionId;
+    auto dst = fs::path(IMG_DIR_PERSIST) / model;
+    try
+    {
+        fs::remove_all(dst);
+        fs::create_directories(dst);
+        fs::copy(src, dst);
+        path(dst.string()); // Update the FilePath interface
+    }
+    catch (const fs::filesystem_error& e)
+    {
+        log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()),
+                        entry("SRC=%s", src.c_str()),
+                        entry("DST=%s", dst.c_str()));
+    }
+}
+
 } // namespace updater
 } // namespace software
 } // namespace phosphor
diff --git a/src/activation.hpp b/src/activation.hpp
index 7a9e464..beab767 100644
--- a/src/activation.hpp
+++ b/src/activation.hpp
@@ -228,6 +228,9 @@
     /** @brief Check if the PSU is comaptible with this software*/
     bool isCompatible(const std::string& psuInventoryPath);
 
+    /** @brief Store the updated PSU image to persistent dir */
+    void storeImage();
+
     /** @brief Persistent sdbusplus DBus bus connection */
     sdbusplus::bus::bus& bus;