hnvram: allow adding variables in RO partition

A previous commit allowed adding new variables into hnvram
at runtime:
go/fibercl/75404.

These variables were automatically placed into the RW partition
and stored as STRING datatype. This commit continues by allowing
variables to be added into the Read Only (RO) partition as well.

hnvram -w NEWVAR=abc123 - Will fail, as before
hnvram -n -w NEWVAR=abc123 - Add to RW partition as STRING
hnvram -n -p RW -w NEWVAR=abc123 - Add to RW partition as STRING
hnvram -n -p RO -w NEWVAR=abc123 - Add to RW partition as STRING

hnvram -k NEWVAR - Will delete NEWVAR from any and all partitions

In reference to b/31999248

Change-Id: Ia3a70c1bc90b27a7d837752b5ef946cdd6ea3ff6
diff --git a/hnvram/hnvram_main.c b/hnvram/hnvram_main.c
index 4fbbf2f..a406caa 100644
--- a/hnvram/hnvram_main.c
+++ b/hnvram/hnvram_main.c
@@ -33,13 +33,14 @@
 }
 
 void usage(const char* progname) {
-  printf("Usage: %s [-d | [-q|-b] [-r|-k] VARNAME] [ [-n] -w VARNAME=value]\n", progname);
+  printf("Usage: %s [-d | [-q|-b] [-r|-k] VARNAME] [ [-n [-p [RO|RW]]] -w VARNAME=value]\n", progname);
   printf("\t-d : dump all NVRAM variables\n");
   printf("\t-r VARNAME : read VARNAME from NVRAM\n");
   printf("\t-q : quiet mode, suppress the variable name and equal sign\n");
   printf("\t-b : read VARNAME from NVRAM in raw binary format, e.g. dumping a binary key\n");
   printf("\t-w VARNAME=value : write value to VARNAME in NVRAM.\n");
   printf("\t-n : toggles whether -w can create new variables. Default is off\n");
+  printf("\t-p [RW|RO] : toggles what partition new writes (-n) used. Default is RW\n");
   printf("\t-k VARNAME : delete existing key/value pair from NVRAM.\n");
 }
 
@@ -190,7 +191,14 @@
   return (int)ret;
 }
 
-char* read_nvram(const char* name, char* output, int outlen, int quiet) {
+// name - name of key to be read
+// output - buffer for value of key
+// outlen - length of buffer
+// quiet - whether buffer is KEY=VAL or VAL
+// part_used - in the case of dynamically added variables (is_field = false),
+//     returns what partition we found the key in
+char* read_nvram(const char* name, char* output, int outlen, int quiet,
+                 HMX_NVRAM_PARTITION_E* part_used) {
   const hnvram_field_t* field = get_nvram_field(name);
   int is_field = (field != NULL);
 
@@ -205,10 +213,18 @@
     }
   } else {
     format_type = HNVRAM_STRING;
-    DRV_Error e = HMX_NVRAM_Read(HMX_NVRAM_PARTITION_RW, (unsigned char*)name,
-                                 0, data, sizeof(data), &data_len);
+
+    // Try both partitions
+    *part_used = HMX_NVRAM_PARTITION_RW;
+    DRV_Error e = HMX_NVRAM_Read(*part_used, (unsigned char*)name, 0, data,
+                                 sizeof(data), &data_len);
     if (e != DRV_OK) {
-      return NULL;
+      *part_used = HMX_NVRAM_PARTITION_RO;
+      e = HMX_NVRAM_Read(*part_used, (unsigned char*)name, 0, data,
+                         sizeof(data), &data_len);
+      if (e != DRV_OK) {
+        return NULL;
+      }
     }
   }
   char formatbuf[NVRAM_MAX_DATA * 2];
@@ -354,25 +370,24 @@
 }
 
 DRV_Error clear_nvram(char* optarg) {
-  DRV_Error e = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RW,
-                                 (unsigned char*)optarg);
-  if (e == DRV_ERR) {
-    // Avoid throwing error message if variable already cleared
+  DRV_Error err1 = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RW,
+                                    (unsigned char*)optarg);
+  DRV_Error err2 = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RO,
+                                    (unsigned char*)optarg);
+
+  // Avoid throwing error message if variable already cleared
+  if ((err1 == DRV_ERR || err1 == DRV_OK) &&
+      (err2 == DRV_ERR || err2 == DRV_OK)) {
     return DRV_OK;
   }
-  return e;
+
+  fprintf(stderr, "Error while deleting key %s. RW: %d RO: %d.\n", optarg,
+          err1, err2);
+  return DRV_ERR;
 }
 
-int write_nvram(char* optarg) {
-  char* equal = strchr(optarg, '=');
-  if (equal == NULL) {
-    return -1;
-  }
 
-  char* name = optarg;
-  *equal = '\0';
-  char* value = ++equal;
-
+int write_nvram(char* name, char* value, HMX_NVRAM_PARTITION_E desired_part) {
   const hnvram_field_t* field = get_nvram_field(name);
   int is_field = (field != NULL);
 
@@ -397,22 +412,61 @@
 
   if (!is_field) {
     char tmp[NVRAM_MAX_DATA] = {0};
-    int key_exists = (read_nvram(name, tmp, NVRAM_MAX_DATA, 1) != NULL);
-    if (!can_add_flag && !key_exists) {
-      fprintf(stderr, "Key not found in NVRAM. Add -n to allow creation %s\n",
-              name);
-      return -3;
+    HMX_NVRAM_PARTITION_E part_used;
+    if (read_nvram(name, tmp, NVRAM_MAX_DATA, 1, &part_used) == NULL) {
+      return -3; // Write failed: Variable not found
     }
-    DRV_Error er = HMX_NVRAM_Write(HMX_NVRAM_PARTITION_RW, (unsigned char*)name,
-                                   0, nvram_value, nvram_len);
-    if (er != DRV_OK) {
+
+    if (desired_part != HMX_NVRAM_PARTITION_UNSPECIFIED &&
+        desired_part != part_used) {
+      fprintf(stderr, "Variable already exists in other partition: %s\n", name);
       return -4;
     }
-  } else {
-    if (HMX_NVRAM_SetField(field->nvram_type, 0,
-                           nvram_value, nvram_len) != DRV_OK) {
+
+    DRV_Error er = HMX_NVRAM_Write(part_used, (unsigned char*)name, 0,
+                                   nvram_value, nvram_len);
+    if (er != DRV_OK) {
       return -5;
     }
+  } else {
+    if (desired_part != HMX_NVRAM_PARTITION_UNSPECIFIED) {
+      fprintf(stderr, "Partition was specified (%d) on a field variable: %s\n",
+              desired_part, name);
+      return -6;
+    }
+    if (HMX_NVRAM_SetField(field->nvram_type, 0,
+                           nvram_value, nvram_len) != DRV_OK) {
+      return -7;
+    }
+  }
+
+  return 0;
+}
+
+// Adds new variable to HNVRAM in desired_partition as STRING
+int write_nvram_new(char* name, char* value,
+                    HMX_NVRAM_PARTITION_E desired_part) {
+  char tmp[NVRAM_MAX_DATA] = {0};
+  unsigned char nvram_value[NVRAM_MAX_DATA];
+  unsigned int nvram_len = sizeof(nvram_value);
+  if (parse_nvram(HNVRAM_STRING, value, nvram_value, &nvram_len) == NULL) {
+    return -1;
+  }
+
+  if (!can_add_flag) {
+    fprintf(stderr, "Key not found in NVRAM. Add -n to allow creation %s\n",
+            name);
+    return -2;
+  }
+
+  if (desired_part == HMX_NVRAM_PARTITION_UNSPECIFIED) {
+    desired_part = HMX_NVRAM_PARTITION_RW;
+  }
+
+  DRV_Error er = HMX_NVRAM_Write(desired_part, (unsigned char*)name, 0,
+                                 nvram_value, nvram_len);
+  if (er != DRV_OK) {
+    return -3;
   }
 
   return 0;
@@ -432,9 +486,11 @@
   int op_cnt = 0;  // operation
   int q_flag = 0;  // quiet: don't output name of variable.
   int b_flag = 0;  // binary: output the binary format
+  // Desired partition for new writes.
+  HMX_NVRAM_PARTITION_E desired_part = HMX_NVRAM_PARTITION_UNSPECIFIED;
   char output[NVRAM_MAX_DATA];
   int c;
-  while ((c = getopt(argc, argv, "dbqrnw:k:")) != -1) {
+  while ((c = getopt(argc, argv, "dbqrnp:w:k:")) != -1) {
     switch(c) {
       case 'b':
         b_flag = 1;
@@ -445,10 +501,35 @@
       case 'n':
         can_add_flag = 1;
         break;
+      case 'p':
+        if (strcmp(optarg, "RO") == 0) {
+          desired_part = HMX_NVRAM_PARTITION_RO;
+        } else if (strcmp(optarg, "RW") == 0) {
+          desired_part = HMX_NVRAM_PARTITION_RW;
+        } else {
+          fprintf(stderr, "Invalid partition: %s. Use RW or RO\n", optarg);
+          exit(1);
+        }
+        break;
       case 'w':
         {
           char* duparg = strdup(optarg);
-          if (write_nvram(duparg) != 0) {
+          char* equal = strchr(duparg, '=');
+          if (equal == NULL) {
+            return -1;
+          }
+
+          char* name = duparg;
+          *equal = '\0';
+          char* value = equal + 1;
+
+          int ret = write_nvram(name, value, desired_part);
+          if (ret == -3 && can_add_flag) {
+            // key not found, and we are authorized to add a new one
+            ret = write_nvram_new(name, value, desired_part);
+          }
+
+          if (ret != 0) {
             fprintf(stderr, "Unable to write %s\n", duparg);
             free(duparg);
             exit(1);
@@ -510,7 +591,8 @@
           }
           fwrite(output, 1, len, stdout);
         } else {
-          if (read_nvram(argv[optind], output, sizeof(output), q_flag) == NULL) {
+          HMX_NVRAM_PARTITION_E part_used;
+          if (read_nvram(argv[optind], output, sizeof(output), q_flag, &part_used) == NULL) {
             fprintf(stderr, "Unable to read %s\n", argv[optind]);
             exit(1);
           }
diff --git a/hnvram/hnvram_test.cc b/hnvram/hnvram_test.cc
index 39f7e4a..643a436 100644
--- a/hnvram/hnvram_test.cc
+++ b/hnvram/hnvram_test.cc
@@ -8,16 +8,31 @@
 
 int libupgrade_verbose = 1;
 
-char* HMX_NVRAM_Read_Data = NULL;
+char* HMX_NVRAM_Read_Data_RO = NULL;
+char* HMX_NVRAM_Read_Data_RW = NULL;
+
+char* get_Read_Data(HMX_NVRAM_PARTITION_E partition) {
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    return HMX_NVRAM_Read_Data_RO;
+  } else {
+    return HMX_NVRAM_Read_Data_RW;
+  }
+}
+
 DRV_Error HMX_NVRAM_Read(HMX_NVRAM_PARTITION_E partition,
                          unsigned char* pName, unsigned int offset,
                          unsigned char* pValue, unsigned int ulSize,
                          unsigned int* pLen) {
-  if (HMX_NVRAM_Read_Data == NULL) {
+  if (get_Read_Data(partition) == NULL) {
     return DRV_ERR;
+  }
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    snprintf((char*)pValue, ulSize, "%s", HMX_NVRAM_Read_Data_RO);
+    *pLen = strlen(HMX_NVRAM_Read_Data_RO);
+    return DRV_OK;
   } else {
-    snprintf((char*)pValue, ulSize, "%s", HMX_NVRAM_Read_Data);
-    *pLen = strlen(HMX_NVRAM_Read_Data);
+    snprintf((char*)pValue, ulSize, "%s", HMX_NVRAM_Read_Data_RW);
+    *pLen = strlen(HMX_NVRAM_Read_Data_RW);
     return DRV_OK;
   }
 }
@@ -25,17 +40,26 @@
 DRV_Error HMX_NVRAM_Write(HMX_NVRAM_PARTITION_E partition,
                          unsigned char* pName, unsigned int offset,
                          unsigned char* pValue, unsigned int ulSize) {
-  HMX_NVRAM_Read_Data = (char*)malloc(ulSize);
-  snprintf(HMX_NVRAM_Read_Data, sizeof(pValue), "%s", (char*)pValue);
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    HMX_NVRAM_Read_Data_RO = (char*)malloc(ulSize);
+    snprintf(HMX_NVRAM_Read_Data_RO, sizeof(pValue), "%s", (char*)pValue);
+  } else {
+    HMX_NVRAM_Read_Data_RW = (char*)malloc(ulSize);
+    snprintf(HMX_NVRAM_Read_Data_RW, sizeof(pValue), "%s", (char*)pValue);
+  }
   return DRV_OK;
 }
 
 DRV_Error HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_E partition,
                            unsigned char* pName) {
-  if (HMX_NVRAM_Read_Data == NULL) {
+  if (get_Read_Data(partition) == NULL) {
     return DRV_ERR;
   }
-  HMX_NVRAM_Read_Data = NULL;
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    HMX_NVRAM_Read_Data_RO = NULL;
+  } else {
+    HMX_NVRAM_Read_Data_RW = NULL;
+  }
   return DRV_OK;
 }
 
@@ -84,7 +108,8 @@
     virtual ~HnvramTest() {}
 
     virtual void SetUp() {
-      HMX_NVRAM_Read_Data = NULL;
+      HMX_NVRAM_Read_Data_RO = NULL;
+      HMX_NVRAM_Read_Data_RW = NULL;
       HMX_NVRAM_GetField_Data = NULL;
       HMX_NVRAM_SetField_Data = NULL;
       HMX_NVRAM_SetField_Len = -1;
@@ -184,104 +209,145 @@
 
 TEST_F(HnvramTest, TestReadFieldNvram) {
   char output[256];
+  HMX_NVRAM_PARTITION_E part;
   HMX_NVRAM_GetField_Data = "TestSystemId";
   EXPECT_STREQ("SYSTEM_ID=TestSystemId",
-               read_nvram("SYSTEM_ID", output, sizeof(output), 0));
+               read_nvram("SYSTEM_ID", output, sizeof(output), 0, &part));
   EXPECT_STREQ("TestSystemId",
-               read_nvram("SYSTEM_ID", output, sizeof(output), 1));
+               read_nvram("SYSTEM_ID", output, sizeof(output), 1, &part));
   HMX_NVRAM_GetField_Data = NULL;
-  EXPECT_EQ(NULL, read_nvram("FAKE_SYSTEM_ID", output, sizeof(output), 1));
+  EXPECT_EQ(NULL, read_nvram("FAKE_SYSTEM_ID", output, sizeof(output), 1,
+                             &part));
 }
 
 TEST_F(HnvramTest, TestReadVariableNvram) {
   char output[256];
-  HMX_NVRAM_Read_Data = strdup("ABC123");
+  HMX_NVRAM_PARTITION_E part;
+  HMX_NVRAM_Read_Data_RW = strdup("ABC123");
   EXPECT_STREQ("TEST_VARIABLE=ABC123",
-               read_nvram("TEST_VARIABLE", output, sizeof(output), 0));
+               read_nvram("TEST_VARIABLE", output, sizeof(output), 0, &part));
+  EXPECT_EQ((int)HMX_NVRAM_PARTITION_RW, part);
   EXPECT_STREQ("ABC123",
-               read_nvram("TEST_VARIABLE", output, sizeof(output), 1));
-  HMX_NVRAM_Read_Data = NULL;
-  EXPECT_STREQ(NULL, read_nvram("TEST_VARIABLE", output, sizeof(output), 1));
+               read_nvram("TEST_VARIABLE", output, sizeof(output), 1, &part));
+  EXPECT_EQ((int)HMX_NVRAM_PARTITION_RW, part);
+  HMX_NVRAM_Read_Data_RW = NULL;
+  EXPECT_STREQ(NULL, read_nvram("TEST_VARIABLE", output, sizeof(output), 1,
+                                &part));
 }
 
 TEST_F(HnvramTest, TestWriteFieldNvram) {
   // Type integer
-  char* testdata = strdup("ACTIVATED_KERNEL_NUM=1");
-  EXPECT_EQ(DRV_OK, write_nvram(testdata));
+  char* key = strdup("ACTIVATED_KERNEL_NUM");
+  char* val = strdup("1");
+  EXPECT_EQ(DRV_OK, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
   EXPECT_EQ(0x01, *HMX_NVRAM_SetField_Data);
   EXPECT_EQ(1, HMX_NVRAM_SetField_Len);
 
   // Type string
-  testdata = strdup("ACTIVATED_KERNEL_NAME=kernel1");
-  EXPECT_EQ(DRV_OK, write_nvram(testdata));
+  key = strdup("ACTIVATED_KERNEL_NAME");
+  val = strdup("kernel1");
+  EXPECT_EQ(DRV_OK, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
   EXPECT_STREQ("kernel1", (char*)HMX_NVRAM_SetField_Data);
   EXPECT_EQ(7, HMX_NVRAM_SetField_Len);
 
   // Make sure it called SetField and not HMX_NVRAM_Write
-  EXPECT_EQ (NULL, HMX_NVRAM_Read_Data);
+  EXPECT_EQ (NULL, HMX_NVRAM_Read_Data_RW);
+  EXPECT_EQ (NULL, HMX_NVRAM_Read_Data_RO);
 
   // Should fail trying to change value of non-exsting field
-  testdata = strdup("FAKE_FIELD=abc123");
-  EXPECT_NE(0, write_nvram(testdata));
-  free(testdata);
+  key = strdup("FAKE_FIELD");
+  val = strdup("abc123");
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  free(key);
+  free(val);
 }
 
-TEST_F(HnvramTest, TestWriteVariableNvram) {
+void testWriteVariableNvram(HMX_NVRAM_PARTITION_E partition, HMX_NVRAM_PARTITION_E other) {
   char* key = strdup("TEST_FIELD");
   char* val = strdup("abc123");
-  char* keyval = strdup("TEST_FIELD=abc123");
 
   // Fail to add new one without -n
-  EXPECT_NE(0, write_nvram(strdup(keyval)));
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_RW));
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_RO));
 
-  // Add new one successfully
   can_add_flag = 1;
-  EXPECT_EQ(0, write_nvram(keyval));
-  EXPECT_STREQ(val,HMX_NVRAM_Read_Data);
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_RO));
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_RW));
+  // Add new one successfully
+  EXPECT_EQ(0, write_nvram_new(key, val, partition));
+  EXPECT_STREQ(val, get_Read_Data(partition));
 
   // Should be able to read value
   char output[256];
-  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1));
+  HMX_NVRAM_PARTITION_E part_used;
+  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1, &part_used));
+
+  // Make sure read came from right partition
+  EXPECT_EQ(partition, part_used);
 
   char* val2 = strdup("987def");
-  char* keyval2 = strdup("TEST_FIELD=987def");
 
   // Should be able to change value
-  EXPECT_EQ(0, write_nvram(keyval2));
-  EXPECT_STREQ(val2,HMX_NVRAM_Read_Data);
+  EXPECT_EQ(0, write_nvram(key, val2, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_STREQ(val2, get_Read_Data(partition));
+
+  // And back again, this time with correct partition specified
+  EXPECT_EQ(0, write_nvram(key, val, partition));
+  EXPECT_STREQ(val, get_Read_Data(partition));
+
+  // Should fail when specifying wrong partition
+  EXPECT_EQ(-4, write_nvram(key, val2, other));
+  EXPECT_EQ(-4, write_nvram(key, val2, HMX_NVRAM_PARTITION_W_RAWFS));
 
   free(key);
   free(val);
-  free(keyval);
   free(val2);
-  free(keyval2);
 }
 
-TEST_F(HnvramTest, TestClearNvram) {
+TEST_F(HnvramTest, TestWriteVariableNvramRO) {
+  testWriteVariableNvram(HMX_NVRAM_PARTITION_RO, HMX_NVRAM_PARTITION_RW);
+}
+
+TEST_F(HnvramTest, TestWriteVariableNvramRW) {
+  testWriteVariableNvram(HMX_NVRAM_PARTITION_RW, HMX_NVRAM_PARTITION_RO);
+}
+
+void testClearNvram(HMX_NVRAM_PARTITION_E partition) {
   char* key = strdup("TEST_FIELD2");
   char* val = strdup("abc123");
-  char* keyval = strdup("TEST_FIELD2=abc123");
   // No error if variable already cleared
   EXPECT_EQ(DRV_OK, clear_nvram(key));
 
-  // Create new one
+  // Create new var
   can_add_flag = 1;
-  EXPECT_EQ(0, write_nvram(keyval));
-  EXPECT_STREQ(val, HMX_NVRAM_Read_Data);
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_EQ(0, write_nvram_new(key, val, partition));
+  EXPECT_STREQ(val, get_Read_Data(partition));
 
   // Should be able to read value
   char output[256];
-  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1));
+  HMX_NVRAM_PARTITION_E part_used;
+  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1, &part_used));
+  EXPECT_EQ((int)partition, part_used);
 
   // Should be able to kill it
   EXPECT_EQ(DRV_OK, clear_nvram(key));
 
   // Should fail reading value
-  EXPECT_STREQ(NULL, read_nvram(key, output, sizeof(output), 1));
+  EXPECT_STREQ(NULL, read_nvram(key, output, sizeof(output), 1, &part_used));
 
   free(key);
   free(val);
-  free(keyval);
+}
+
+TEST_F(HnvramTest, TestClearNvramRO) {
+  testClearNvram(HMX_NVRAM_PARTITION_RO);
+}
+
+TEST_F(HnvramTest, TestClearNvramRW) {
+  testClearNvram(HMX_NVRAM_PARTITION_RW);
 }
 
 int main(int argc, char** argv) {