vpnor: Add patch location

Look for the requested partition in the designated patch location
(/usr/local/share/pnor/). If the partition is not found there,
continue with the existing logic of looking in the other partition
directories.
This allows users to use patches in the virtual pnor implementation.

Resolves openbmc/openbmc#1551

Change-Id: I7f27dfc9cd69a3f8ab88cb6fa77b2c1096e32841
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/configure.ac b/configure.ac
index 94047da..5ab386f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -79,6 +79,7 @@
 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.])
+AC_DEFINE(PARTITION_FILES_PATCH_LOC, "/usr/local/share/pnor", [The path to the directory containing PNOR patch partition files.])
 # Create configured output
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/mboxd_pnor_partition_table.cpp b/mboxd_pnor_partition_table.cpp
index 2de790b..e2125da 100644
--- a/mboxd_pnor_partition_table.cpp
+++ b/mboxd_pnor_partition_table.cpp
@@ -18,6 +18,7 @@
         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);
+        strcpy(context->paths.patch_loc, PARTITION_FILES_PATCH_LOC);
 
         context->vpnor = new vpnor_partition_table;
         context->vpnor->table =
diff --git a/mboxd_pnor_partition_table.h b/mboxd_pnor_partition_table.h
index 452a871..738524b 100644
--- a/mboxd_pnor_partition_table.h
+++ b/mboxd_pnor_partition_table.h
@@ -13,6 +13,7 @@
     char ro_loc[PATH_MAX];
     char rw_loc[PATH_MAX];
     char prsv_loc[PATH_MAX];
+    char patch_loc[PATH_MAX];
 };
 
 #ifdef __cplusplus
diff --git a/pnor_partition.cpp b/pnor_partition.cpp
index ba0ac20..1d5ca12 100644
--- a/pnor_partition.cpp
+++ b/pnor_partition.cpp
@@ -69,6 +69,14 @@
 
     fs::path partitionFilePath;
 
+    // Check if partition exists in patch location
+    partitionFilePath = context->paths.patch_loc;
+    partitionFilePath /= partition->data.name;
+    if (fs::is_regular_file(partitionFilePath))
+    {
+        return partitionFilePath.string();
+    }
+
     switch (partition->data.user.data[1] &
             (PARTITION_PRESERVED | PARTITION_READONLY))
     {
diff --git a/test/write_flash_vpnor.cpp b/test/write_flash_vpnor.cpp
index 30c9ff7..ceb24aa 100644
--- a/test/write_flash_vpnor.cpp
+++ b/test/write_flash_vpnor.cpp
@@ -60,7 +60,8 @@
 
     std::vector<std::string> templatePaths = { "/tmp/ro.XXXXXX",
                                                "/tmp/rw.XXXXXX" ,
-                                               "/tmp/prsv.XXXXXX"
+                                               "/tmp/prsv.XXXXXX",
+                                               "/tmp/patch.XXXXXX"
                                              };
 
     std::vector<std::string>partitions = { "TEST1", "TEST2", "TEST3" };
@@ -77,6 +78,9 @@
     std::string tmpPRSVdir = mkdtemp(const_cast<char*>(templatePaths[2].c_str()));
     assert(tmpPRSVdir.length() != 0);
 
+    std::string tmpPATCHdir = mkdtemp(const_cast<char*>(templatePaths[3].c_str()));
+    assert(tmpPATCHdir.length() != 0);
+
     // create the toc file
     fs::path tocFilePath = tmpROdir;
 
@@ -114,6 +118,7 @@
     strcpy(ctx->paths.ro_loc, tmpROdir.c_str());
     strcpy(ctx->paths.rw_loc, tmpRWdir.c_str());
     strcpy(ctx->paths.prsv_loc, tmpPRSVdir.c_str());
+    strcpy(ctx->paths.patch_loc, tmpPATCHdir.c_str());
 
 }
 
@@ -134,6 +139,7 @@
     std::string tmpROdir = ctx->paths.ro_loc;
     std::string tmpRWdir = ctx->paths.rw_loc;
     std::string tmpPRSVdir = ctx->paths.prsv_loc;
+    std::string tmpPATCHdir = ctx->paths.patch_loc;
 
     // create the partition table
     vpnor_create_partition_table_from_path(ctx, tmpROdir.c_str());
@@ -209,9 +215,53 @@
     rc = memcmp(src, map, sizeof(src));
     assert(rc == 0);
 
+    munmap(map, MEM_SIZE);
+    close(fd);
+
+    // START Test patch location - Patch dir has preference over other locations
+    // Copy partition2 file from ro to patch to simulate a patch file that is
+    // different from the one in rw (partition2 in rw was modified with the
+    // previous write test)
+    fs::path roFile(tmpROdir);
+    roFile /= "TEST2";
+    fs::path patchFile(tmpPATCHdir);
+    patchFile /= "TEST2";
+    assert(fs::copy_file(roFile, patchFile) == true);
+
+    // Write arbitrary data
+    char srcPatch[DATA_SIZE] {0};
+    memset(srcPatch, 0x33, sizeof(srcPatch));
+    rc = write_flash(ctx, (OFFSET * 2), srcPatch, sizeof(srcPatch));
+    assert(rc == 0);
+
+    // Check that partition file in RW location still contains the original data
+    fs::path rwFile(tmpRWdir);
+    rwFile /= "TEST2";
+    fd = open(rwFile.c_str(), O_RDONLY);
+    map = mmap(NULL, MEM_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
+    assert(map != MAP_FAILED);
+    rc = memcmp(src, map, sizeof(src));
+    assert(rc == 0);
+    munmap(map, MEM_SIZE);
+    close(fd);
+
+    // Check that partition file in PATCH location was written with the new data
+    fd = open(patchFile.c_str(), O_RDONLY);
+    map = mmap(NULL, MEM_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
+    assert(map != MAP_FAILED);
+    rc = memcmp(srcPatch, map, sizeof(srcPatch));
+    assert(rc == 0);
+    munmap(map, MEM_SIZE);
+    close(fd);
+
+    // Remove patch file so that subsequent tests don't use it
+    fs::remove(patchFile);
+    // END Test patch location
+
     fs::remove_all(fs::path {tmpROdir});
     fs::remove_all(fs::path {tmpRWdir});
     fs::remove_all(fs::path {tmpPRSVdir});
+    fs::remove_all(fs::path {tmpPATCHdir});
 
     return rc;
 }