Remove factory.* items from VPD in finalize.

BUG=chrome-os-partner:19472
TEST=unit tests, manual on device

Change-Id: Ia2df23b4a6291fbe88fb39f6fdef95905227d7f6
Reviewed-on: https://gerrit.chromium.org/gerrit/51272
Tested-by: Jon Salz <jsalz@chromium.org>
Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Commit-Queue: Jon Salz <jsalz@chromium.org>
diff --git a/py/gooftool/__init__.py b/py/gooftool/__init__.py
index 63463c9..c547109 100644
--- a/py/gooftool/__init__.py
+++ b/py/gooftool/__init__.py
@@ -4,6 +4,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import logging
 import os
 import re
 
@@ -22,7 +23,9 @@
 from cros.factory.hwid import Database
 from cros.factory.hwid.decoder import Decode
 from cros.factory.hwid.encoder import Encode
+from cros.factory.privacy import FilterDict
 from cros.factory.rule import Context
+from cros.factory.system import vpd
 
 # A named tuple to store the probed component name and the error if any.
 ProbedComponentResult = namedtuple('ProbedComponentResult',
@@ -633,10 +636,10 @@
 
     hwid = self._hwid_decode(self.db, encoded_string)
     hwid.VerifyProbeResult(probe_results.Encode())
-    vpd = {'ro': {}, 'rw': {}}
-    vpd['ro'].update(probed_ro_vpd)
-    vpd['rw'].update(probed_rw_vpd)
-    context = Context(hwid=hwid, vpd=vpd)
+    vpd_dict = {'ro': {}, 'rw': {}}
+    vpd_dict['ro'].update(probed_ro_vpd)
+    vpd_dict['rw'].update(probed_rw_vpd)
+    context = Context(hwid=hwid, vpd=vpd_dict)
     self.db.rules.EvaluateRules(context, namespace="verify.*")
 
   def DecodeHwidV3(self, encoded_string):
@@ -677,3 +680,14 @@
     """
     # TODO(jcliang): Re-implement this after rule language refactoring.
     pass
+
+  def ClearFactoryVPDEntries(self):
+    """Clears factory.* items in the RW VPD.
+
+    Returns:
+      A dict of the removed entries.
+    """
+    entries = dict((k, v) for k, v in vpd.rw.GetAll().items()
+                   if k.startswith('factory.'))
+    logging.info('Removing VPD entries %s', FilterDict(entries))
+    vpd.rw.Delete(*entries.keys())
diff --git a/py/gooftool/gooftool.py b/py/gooftool/gooftool.py
index 6c140e3..8ee64da 100755
--- a/py/gooftool/gooftool.py
+++ b/py/gooftool/gooftool.py
@@ -576,6 +576,13 @@
   event_log.Log('clear_gbb_flags')
 
 
+@Command('clear_factory_vpd_entries')
+def ClearFactoryVPDEntries(options):  # pylint: disable=W0613
+  """Clears factory.* items in the RW VPD."""
+  entries = GetGooftool(options).ClearFactoryVPDEntries()
+  event_log.Log('clear_factory_vpd_entries', entries=FilterDict(entries))
+
+
 @Command('prepare_wipe',
          CmdArg('--fast', action='store_true',
                 help='use non-secure but faster wipe method.'))
@@ -705,17 +712,20 @@
 def Finalize(options):
   """Verify system readiness and trigger transition into release state.
 
-  This routine first verifies system state (see verify command), modifies
-  firmware bitmaps to match locale, and then clears all of the factory-friendly
-  flags from the GBB.  If everything is fine, it enables firmware write
-  protection (cannot rollback after this stage), uploads system logs & reports,
-  and sets the necessary boot flags to cause wipe of the factory image on the
-  next boot.
+  This routine does the following:
+  - Verifies system state (see verify command)
+  - Modifies firmware bitmaps to match locale
+  - Clears all factory-friendly flags from the GBB
+  - Removes factory-specific entries from RW_VPD (factory.*)
+  - Enables firmware write protection (cannot rollback after this)
+  - Uploads system logs & reports
+  - Sets the necessary boot flags to cause wipe of the factory image on the
+    next boot.
   """
-
   Verify(options)
   SetFirmwareBitmapLocale(options)
   ClearGBBFlags(options)
+  ClearFactoryVPDEntries(options)
   if options.no_write_protect:
     logging.warn('WARNING: Firmware Write Protection is SKIPPED.')
     event_log.Log('wp', fw='both', status='skipped')
diff --git a/py/system/vpd.py b/py/system/vpd.py
index dcbe788..44099f1 100644
--- a/py/system/vpd.py
+++ b/py/system/vpd.py
@@ -47,6 +47,17 @@
     """
     return self.GetAll().get(key, default)
 
+  def Delete(self, *keys):
+    """Deletes entries from the VPD.
+
+    Raises:
+      An error if any entries cannot be deleted.  In this case some or
+      all other entries may have been deleted.
+    """
+    for k in keys:
+      Spawn(['vpd', '-i', self.name, '-d', k], check_call=True,
+            log_stderr_on_error=True)
+
   def GetAll(self):
     """Returns the contents of the VPD as a dict."""
     ret = {}
diff --git a/py/system/vpd_unittest.py b/py/system/vpd_unittest.py
index 934a7c7..1a664b4 100755
--- a/py/system/vpd_unittest.py
+++ b/py/system/vpd_unittest.py
@@ -100,6 +100,36 @@
                             vpd.rw.Update, {'a': '\"'}, log=False)
     self.mox.VerifyAll()
 
+  def testDeleteNone(self):
+    self.mox.ReplayAll()
+    vpd.rw.Delete()  # no-op
+    self.mox.VerifyAll()
+
+  def testDeleteOne(self):
+    vpd.Spawn(
+        ['vpd', '-i', 'RW_VPD', '-d', 'a'],
+        check_call=True, log_stderr_on_error=True)
+    self.mox.ReplayAll()
+    vpd.rw.Delete('a')
+    self.mox.VerifyAll()
+
+  def testDeleteTwo(self):
+    for k in ['a', 'b']:
+      vpd.Spawn(
+          ['vpd', '-i', 'RW_VPD', '-d', k],
+          check_call=True, log_stderr_on_error=True)
+    self.mox.ReplayAll()
+    vpd.rw.Delete('a', 'b')
+    self.mox.VerifyAll()
+
+  def testDeleteError(self):
+    vpd.Spawn(
+        ['vpd', '-i', 'RW_VPD', '-d', 'a'],
+        check_call=True, log_stderr_on_error=True).AndRaise(ValueError)
+    self.mox.ReplayAll()
+    self.assertRaises(ValueError, vpd.rw.Delete, 'a')
+    self.mox.VerifyAll()
+
 
 if __name__ == '__main__':
   unittest2.main()