Read/write window property defines FFS partition file location.

The BMC loads the PNOR partitions in a read-only volume.

So after that there are following two cases:

Read: Tries to open the file in read only mode from
the partition(READONLY/READWRITE/PRESERVED), Partition
Table tells the partition type depends on the offset.
if file is not there in the mapped partition
then tries to open the file from the read only partition.

Write: Tries to open the file in write mode from the
partition(READWRITE/PRESERVED), if file is not there
in the mapped partition then mailbox daemon will copy
the requested partition to the read-write volume to
make changes to it.

Change-Id: Ic0ef882380b56536ac55feae3ec563de95fdd4a6
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 5614256..4f49644 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -16,7 +16,8 @@
 if VIRTUAL_PNOR_ENABLED
 mboxd_SOURCES += pnor_partition_table.cpp \
 	mboxd_pnor_partition_table.cpp \
-	mboxd_flash_virtual.cpp
+	mboxd_flash_virtual.cpp \
+	pnor_partition.cpp
 
 mboxd_LDFLAGS += -lstdc++fs \
 	$(SDBUSPLUS_LIBS) \
@@ -39,13 +40,35 @@
 
 test_sanity_SOURCES = test/sanity.c
 
-test_copy_flash_SOURCES = test/copy_flash.c mboxd_flash.c mboxd_flash_physical.c common.c mtd.c test/tmpf.c
+test_copy_flash_SOURCES = \
+	test/copy_flash.c \
+	mboxd_flash.c \
+	mboxd_flash_physical.c \
+	common.c mtd.c \
+	test/tmpf.c
 
-test_erase_flash_SOURCES = test/erase_flash.c mboxd_flash.c common.c test/tmpf.c
+test_erase_flash_SOURCES = \
+	test/erase_flash.c \
+	mboxd_flash.c \
+	mboxd_flash_physical.c \
+	common.c \
+	test/tmpf.c
 
-test_write_flash_SOURCES = test/write_flash.c mboxd_flash.c common.c test/tmpf.c
+test_write_flash_SOURCES = \
+	test/write_flash.c \
+	mboxd_flash.c \
+	mboxd_flash_physical.c \
+	common.c \
+	test/tmpf.c
 
-TEST_MBOX_SRCS = mboxd_msg.c mboxd_windows.c mboxd_lpc.c mboxd_flash.c common.c mboxd_flash_physical.c
+TEST_MBOX_SRCS = \
+	mboxd_msg.c \
+	mboxd_windows.c \
+	mboxd_lpc.c \
+	mboxd_flash.c \
+	common.c \
+	mboxd_flash_physical.c
+
 TEST_MOCK_SRCS = test/tmpf.c test/mbox.c test/system.c
 
 test_get_mbox_info_v2_SOURCES = test/get_mbox_info_v2.c \
@@ -133,9 +156,13 @@
 	mboxd_flash.c \
 	mboxd_pnor_partition_table.cpp \
 	mboxd_flash_virtual.cpp \
+	pnor_partition.cpp \
 	test/create_read_window_vpnor.cpp
 test_create_read_window_vpnor_LDFLAGS = $(OESDK_TESTCASE_FLAGS)
-test_create_read_window_vpnor_LDADD = -lstdc++fs
+test_create_read_window_vpnor_LDADD = -lstdc++fs \
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS)
 
 check_PROGRAMS = test/sanity \
 		 test/copy_flash \
@@ -166,7 +193,9 @@
 		 test/get_mbox_info_v2_timeout
 
 if VIRTUAL_PNOR_ENABLED
-check_PROGRAMS += test/create_pnor_partition_table test/create_read_window_vpnor
+check_PROGRAMS += \
+	test/create_pnor_partition_table \
+	test/create_read_window_vpnor
 endif
 
 TESTS = $(check_PROGRAMS)
diff --git a/configure.ac b/configure.ac
index 2f51e2b..94047da 100644
--- a/configure.ac
+++ b/configure.ac
@@ -76,8 +76,9 @@
 AC_SUBST([LIBSYSTEMD_LIBS])
 
 AC_DEFINE(PARTITION_TOC_FILE, "pnor.toc", [The basename of the PNOR Table of contents file.])
-AC_DEFINE(PARTITION_FILES_LOC, "/var/lib/phosphor-software-manager/pnor/ro", [The path to the directory containing PNOR partition files.])
-
+AC_DEFINE(PARTITION_FILES_RO_LOC, "/var/lib/phosphor-software-manager/pnor/ro", [The path to the directory containing PNOR read only partition files.])
+AC_DEFINE(PARTITION_FILES_RW_LOC, "/var/lib/phosphor-software-manager/pnor/rw", [The path to the directory containing PNOR read write partition files.])
+AC_DEFINE(PARTITION_FILES_PRSV_LOC, "/var/lib/phosphor-software-manager/pnor/prsv", [The path to the directory containing PNOR preserve partition files.])
 # Create configured output
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/mboxd.c b/mboxd.c
index fcc3688..e1f3251 100644
--- a/mboxd.c
+++ b/mboxd.c
@@ -314,7 +314,10 @@
 
 #ifdef VIRTUAL_PNOR_ENABLED
 	vpnor_create_partition_table(context);
-	strcpy(context->paths.ro_loc, PARTITION_FILES_LOC);
+
+	strcpy(context->paths.ro_loc, PARTITION_FILES_RO_LOC);
+	strcpy(context->paths.rw_loc, PARTITION_FILES_RW_LOC);
+	strcpy(context->paths.prsv_loc, PARTITION_FILES_PRSV_LOC);
 #endif
 
 	rc = init_signals(context, &set);
diff --git a/mboxd_pnor_partition_table.h b/mboxd_pnor_partition_table.h
index f494830..e6c899b 100644
--- a/mboxd_pnor_partition_table.h
+++ b/mboxd_pnor_partition_table.h
@@ -11,6 +11,8 @@
 struct vpnor_partition_paths
 {
     char ro_loc[PATH_MAX];
+    char rw_loc[PATH_MAX];
+    char prsv_loc[PATH_MAX];
 };
 
 #ifdef __cplusplus
diff --git a/pnor_partition.cpp b/pnor_partition.cpp
new file mode 100644
index 0000000..3f4fd02
--- /dev/null
+++ b/pnor_partition.cpp
@@ -0,0 +1,187 @@
+#include "pnor_partition.hpp"
+#include "config.h"
+#include "mboxd_flash.h"
+#include "mboxd_pnor_partition_table.h"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include "common.h"
+
+#include <string>
+#include <exception>
+#include <stdexcept>
+#include <iostream>
+
+namespace openpower
+{
+namespace virtual_pnor
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using namespace std::string_literals;
+
+ReturnCode Request::open(const std::string& path,
+                         int mode)
+{
+    if (mode == O_RDWR &&
+        partition->data.user.data[1] & PARTITION_READONLY)
+    {
+        MSG_ERR("Can't open the RO partition for write");
+        return ReturnCode::PARTITION_READ_ONLY;
+    }
+
+    fs::path partitionFilePath = path;
+
+    if (!fs::exists(partitionFilePath))
+    {
+        return ReturnCode::FILE_NOT_FOUND;
+    }
+
+    auto descriptor = ::open(partitionFilePath.c_str(), mode);
+    if (descriptor < 0)
+    {
+        return ReturnCode::FILE_OPEN_FAILURE;
+    }
+
+    fd.set(descriptor);
+    descriptor = -1;
+
+    return ReturnCode::SUCCESS;
+}
+
+std::string Request::getPartitionFilePath(struct mbox_context* context,
+                                          uint32_t offset)
+{
+    partition = vpnor_get_partition(context, offset);
+    if (!partition)
+    {
+        MSG_ERR("Couldn't get the partition info for offset[0x%.8x]",offset);
+        elog<InternalFailure>();
+    }
+
+    fs::path partitionFilePath;
+
+    switch (partition->data.user.data[1] &
+            (PARTITION_PRESERVED | PARTITION_READONLY))
+    {
+        case PARTITION_PRESERVED:
+            partitionFilePath = context->paths.prsv_loc;
+            break;
+
+        case PARTITION_READONLY:
+            partitionFilePath = context->paths.ro_loc;
+            break;
+
+        default:
+            partitionFilePath = context->paths.rw_loc;
+    }
+    partitionFilePath /= partition->data.name;
+    return partitionFilePath.string();
+}
+
+const pnor_partition* RORequest::getPartitionInfo(struct mbox_context* context,
+                                                  uint32_t offset)
+{
+    std::string path = getPartitionFilePath(context, offset);
+
+    ReturnCode rc = Request::open(path, O_RDONLY);
+    if (rc == ReturnCode::SUCCESS)
+    {
+        return partition;
+    }
+    // not interested in any other error except FILE_NOT_FOUND
+    if (rc != ReturnCode::FILE_NOT_FOUND)
+    {
+        elog<InternalFailure>();
+    }
+
+    // if the offset lies in read only partition then throw error.
+    if (partition->data.user.data[1] & PARTITION_READONLY)
+    {
+        MSG_ERR("Can't open the partition file");
+        elog<InternalFailure>();
+    }
+
+    MSG_DBG("Couldn't open the file[%s]", path.c_str());
+    // we don't get the file in the respective partition(RW/PSRV)
+    // try to open it from RO location.
+
+    fs::path partitionFilePath = context->paths.ro_loc;
+    partitionFilePath /= partition->data.name;
+
+    rc = Request::open(path, O_RDONLY);
+    if (rc != ReturnCode::SUCCESS)
+    {
+        elog<InternalFailure>();
+    }
+
+    return partition;
+
+}
+
+const pnor_partition* RWRequest::getPartitionInfo(struct mbox_context* context,
+                                                  uint32_t offset)
+{
+    std::string path = getPartitionFilePath(context, offset);
+
+    ReturnCode rc = Request::open(path, O_RDWR);
+    if (rc == ReturnCode::SUCCESS)
+    {
+        return partition;
+    }
+    // not interested in any other error except FILE_NOT_FOUND
+    if (rc != ReturnCode::FILE_NOT_FOUND)
+    {
+        elog<InternalFailure>();
+    }
+
+    // if the file is not available in the respective(RW/PSRV) partition
+    // then copy the file from RO to the respective(RW/PSRV) partition
+    // and open it for writing.
+
+    fs::path fromPath = context->paths.ro_loc;
+    fromPath /= partition->data.name;
+    if (!fs::exists(fromPath))
+    {
+        MSG_ERR("Couldn't find the file[%s]",fromPath.c_str());
+        elog<InternalFailure>();
+    }
+    //copy the file from ro to respective partition
+    fs::path toPath = context->paths.rw_loc;
+
+    if (partition->data.user.data[1] & PARTITION_PRESERVED)
+    {
+        toPath = context->paths.prsv_loc;
+    }
+
+    toPath /= partition->data.name;
+
+    MSG_DBG("Didn't find the file in the desired partition so copying[%s]\n",
+            toPath.c_str());
+
+    if (fs::copy_file(fromPath, toPath))
+    {
+        MSG_DBG("File copied from[%s] to [%s]\n",
+                fromPath.c_str(), toPath.c_str());
+    }
+
+    rc  = Request::open(toPath.c_str(), O_RDWR);
+
+    if (rc != ReturnCode::SUCCESS)
+    {
+        elog<InternalFailure>();
+    }
+
+    return partition;
+}
+
+}// namespace virtual_pnor
+}// namespace openpower
diff --git a/pnor_partition.hpp b/pnor_partition.hpp
new file mode 100644
index 0000000..004dca6
--- /dev/null
+++ b/pnor_partition.hpp
@@ -0,0 +1,162 @@
+#pragma once
+
+#include "mboxd_pnor_partition_table.h"
+#include <fcntl.h>
+#include <string>
+#include <unistd.h>
+#include <experimental/filesystem>
+
+namespace openpower
+{
+namespace file
+{
+
+class Descriptor
+{
+    private:
+        /** default value */
+        int fd = -1;
+
+    public:
+        Descriptor() = default;
+        Descriptor(const Descriptor&) = delete;
+        Descriptor& operator=(const Descriptor&) = delete;
+        Descriptor(Descriptor&&) = delete;
+        Descriptor& operator=(Descriptor &&) = delete;
+
+        Descriptor(int fd) : fd(fd) {}
+
+        ~Descriptor()
+        {
+            if (fd >= 0)
+            {
+                close(fd);
+            }
+        }
+
+        int operator()() const
+        {
+            return fd;
+        }
+
+        void set(int descriptor)
+        {
+            fd = descriptor;
+        }
+};
+
+}// namespace file
+
+namespace virtual_pnor
+{
+
+namespace fs = std::experimental::filesystem;
+
+enum class ReturnCode : uint8_t
+{
+    FILE_NOT_FOUND = 0,
+    PARTITION_NOT_FOUND = 1,
+    PARTITION_READ_ONLY = 2,
+    FILE_OPEN_FAILURE = 3,
+    SUCCESS = 4,
+};
+
+class Request
+{
+    public:
+
+        Request() = default;
+        Request(const Request&) = delete;
+        Request& operator=(const Request&) = delete;
+        Request(Request&&) = default;
+        Request& operator=(Request&&) = default;
+        ~Request() = default;
+
+        openpower::file::Descriptor fd;
+
+    protected:
+        /** @brief opens the partition file
+         *
+         *  @param[in] filePath - Absolute file path.
+         *  @param[in] mode - File open mode.
+         */
+        ReturnCode open(const std::string& filePath, int mode);
+
+        /** @brief returns the partition file path associated with the offset.
+         *
+         *  @param[in] context - The mbox context pointer.
+         *  @param[in] offset - The pnor offset(bytes).
+         */
+
+        std::string getPartitionFilePath(struct mbox_context* context,
+                                         uint32_t offset);
+
+        const pnor_partition* partition = nullptr;
+};
+
+/** @class RORequest
+ *  @brief Represent the read request of the partition.
+ *         Stores the partition meta data.
+ */
+class RORequest : public Request
+{
+    public:
+        RORequest() = default;
+        RORequest(const RORequest&) = delete;
+        RORequest& operator=(const RORequest&) = delete;
+        RORequest(RORequest&&) = default;
+        RORequest& operator=(RORequest&&) = default;
+        ~RORequest(){};
+
+        /** @brief opens the partition file associated with the offset
+         *         in read only mode and gets the partition details.
+         *
+         *  1.  Depending on the partition type,tries to open the file
+         *      from the associated partition(RW/PRSV/RO).
+         *  1a. if file not found in the corresponding
+         *      partition(RW/PRSV/RO) then tries to read the file from
+         *      the read only partition.
+         *  1b. if the file not found in the read only partition then
+         *      throw exception.
+         *
+         *  @param[in] context - The mbox context pointer.
+         *  @param[in] offset - The pnor offset(bytes).
+         */
+        const pnor_partition* getPartitionInfo(struct mbox_context* context,
+                                               uint32_t offset);
+};
+
+/** @class RWRequest
+ *  @brief Represent the write request of the partition.
+ *         Stores the partition meta data.
+ */
+class RWRequest : public Request
+{
+    public:
+
+        RWRequest() = default;
+        RWRequest(const RWRequest&) = delete;
+        RWRequest& operator=(const RWRequest&) = delete;
+        RWRequest(RWRequest&&) = default;
+        RWRequest& operator=(RWRequest&&) = default;
+        ~RWRequest() {};
+
+        /** @brief opens the partition file associated with the offset
+         *         in write mode and gets the parttition details.
+         *
+         *  1.  Depending on the partition type tries to open the file
+         *      from the associated partition.
+         *  1a. if file not found in the corresponding partition(RW/PRSV)
+         *      then copy the file from the read only partition to the (RW/PRSV)
+         *      partition depending on the partition type.
+         *  1b. if the file not found in the read only partition then throw exception.
+         *
+         *  @param[in] context - The mbox context pointer.
+         *  @param[in] offset - The pnor offset(bytes).
+         */
+        const pnor_partition* getPartitionInfo(struct mbox_context* context,
+                                               uint32_t offset);
+};
+
+}// namespace virtual_pnor
+}// namespace openpower
diff --git a/pnor_partition_table.cpp b/pnor_partition_table.cpp
index 17f5011..d20b44c 100644
--- a/pnor_partition_table.cpp
+++ b/pnor_partition_table.cpp
@@ -23,7 +23,7 @@
 } // namespace block
 
 Table::Table():
-    Table(fs::path(PARTITION_FILES_LOC))
+    Table(fs::path(PARTITION_FILES_RO_LOC))
 {
 }
 
diff --git a/test/create_read_window_vpnor.cpp b/test/create_read_window_vpnor.cpp
index 855d88d..8121498 100644
--- a/test/create_read_window_vpnor.cpp
+++ b/test/create_read_window_vpnor.cpp
@@ -66,6 +66,8 @@
 
     struct mbox_context *ctx = mbox_create_test_context(N_WINDOWS, WINDOW_SIZE);
     strcpy(ctx->paths.ro_loc,tmpdir);
+    strcpy(ctx->paths.rw_loc,tmpdir);
+    strcpy(ctx->paths.prsv_loc,tmpdir);
 
     vpnor_create_partition_table_from_path(ctx, tmpdir);