Merge "ginstall: Add support for Android images"
diff --git a/ginstall/ginstall.py b/ginstall/ginstall.py
index f03dfa3..67f196a 100755
--- a/ginstall/ginstall.py
+++ b/ginstall/ginstall.py
@@ -62,10 +62,13 @@
 
 F = {
     'ETCPLATFORM': '/etc/platform',
+    'ETCOS': '/etc/os',
     'ETCVERSION': '/etc/version',
     'DEV': '/dev',
     'MMCBLK0': '/dev/mmcblk0',
+    'MMCBLK0-ANDROID': '/dev/block/mmcblk0',
     'MTD_PREFIX': '/dev/mtd',
+    'MTD_PREFIX-ANDROID': '/dev/mtd/mtd',
     'PROC_CMDLINE': '/proc/cmdline',
     'PROC_MTD': '/proc/mtd',
     'SECUREBOOT': '/tmp/gpio/ledcontrol/secure_boot',
@@ -74,12 +77,22 @@
     'SYSBLOCK': '/sys/block',
     'MMCBLK0BOOT0': '/dev/mmcblk0boot0',
     'MMCBLK0BOOT1': '/dev/mmcblk0boot1',
+    'MMCBLK0BOOT0-ANDROID': '/dev/block/mmcblk0boot0',
+    'MMCBLK0BOOT1-ANDROID': '/dev/block/mmcblk0boot1',
     'MEMINFO': '/proc/meminfo',
 }
 
+ANDROID_BSU_PARTITION = 'bsu'
+ANDROID_BOOT_PARTITIONS = ['boot_a', 'boot_b']
+ANDROID_SYSTEM_PARTITIONS = ['system_a', 'system_b']
+ANDROID_IMAGES = ['boot.img', 'system.img.raw']
+ANDROID_IMG_SUFFIX = ['a', 'b']
+
 MMC_RO_LOCK = {
     'MMCBLK0BOOT0': '/sys/block/mmcblk0boot0/force_ro',
     'MMCBLK0BOOT1': '/sys/block/mmcblk0boot1/force_ro',
+    'MMCBLK0BOOT0-ANDROID': '/sys/block/mmcblk0boot0/force_ro',
+    'MMCBLK0BOOT1-ANDROID': '/sys/block/mmcblk0boot1/force_ro',
 }
 
 # Verbosity of output
@@ -131,6 +144,26 @@
   return open(F['ETCPLATFORM']).read().strip()
 
 
+def GetOs():
+  # not all platforms provide ETCOS, default to 'fiberos' in that case
+  try:
+    return open(F['ETCOS']).read().strip()
+  except IOError:
+    return 'fiberos'
+
+
+def GetMtdPrefix():
+  if GetOs() == 'android':
+    return F['MTD_PREFIX-ANDROID']
+  return F['MTD_PREFIX']
+
+
+def GetMmcblk0Prefix():
+  if GetOs() == 'android':
+    return F['MMCBLK0-ANDROID']
+  return F['MMCBLK0']
+
+
 def GetVersion():
   return open(F['ETCVERSION']).read().strip()
 
@@ -154,17 +187,47 @@
   return None
 
 
-def SetBootPartition(partition):
-  VerbosePrint('Setting boot partition to kernel%d\n', partition)
-  cmd = [HNVRAM, '-q', '-w', 'ACTIVATED_KERNEL_NAME=kernel%d' % partition]
-  return subprocess.call(cmd)
+def SetBootPartition(target_os, partition):
+  """Set active boot partition for the given OS and switch the OS if needed.
+
+  Args:
+    target_os: 'fiberos' or 'android'
+    partition: 0 or 1
+
+  Returns:
+    0 if successful, else an error code.
+  """
+  if target_os == 'android':
+    param = 'ANDROID_ACTIVE_PARTITION=%s' % ANDROID_IMG_SUFFIX[partition]
+  else:
+    param = 'ACTIVATED_KERNEL_NAME=kernel%d' % partition
+
+  VerbosePrint('Setting boot partition: %s\n', param)
+  try:
+    ret = subprocess.call([HNVRAM, '-q', '-w', param])
+  except OSError:
+    ret = 127
+  if ret:
+    VerbosePrint('Failed setting boot partition!\n')
+    return ret
+
+  if target_os != GetOs():
+    VerbosePrint('Switch OS to %s\n', target_os)
+    try:
+      ret = subprocess.call([HNVRAM, '-q', '-w', 'BOOT_TARGET=%s' % target_os])
+    except OSError:
+      ret = 127
+    if ret:
+      VerbosePrint('Failed switching OS!\n')
+
+  return ret
 
 
 def GetBootedPartition():
   """Get the role of partition where the running system is booted from.
 
   Returns:
-    0 or 1 for rootfs0 and rootfs1, or None if not booted from flash.
+    0 or 1, or None if not booted from flash.
   """
   try:
     with open(F['PROC_CMDLINE']) as f:
@@ -184,6 +247,41 @@
         return 0
       elif partition == 'kernel1':
         return 1
+    elif arg.startswith('androidboot.gfiber_system_img='):
+      partition = arg.split('=')[1]
+      if partition == ANDROID_SYSTEM_PARTITIONS[0]:
+        return 0
+      elif partition == ANDROID_SYSTEM_PARTITIONS[1]:
+        return 1
+  return None
+
+
+def GetActivePartitionFromHNVRAM(target_os):
+  """Get the active partion for the given OS as set in HNVRAM.
+
+  Args:
+    target_os: 'fiberos' or 'android'
+
+  Returns:
+    0 or 1 if the active partition could be determined, None if not.
+  """
+  if target_os == 'fiberos':
+    cmd = [HNVRAM, '-q', '-r', 'ACTIVATED_KERNEL_NAME']
+  elif target_os == 'android':
+    cmd = [HNVRAM, '-q', '-r', 'ANDROID_ACTIVE_PARTITION']
+  else:
+    return None
+
+  try:
+    partition_name = subprocess.check_output(cmd).strip()
+  except subprocess.CalledProcessError:
+    return None
+
+  if partition_name in ['0', 'a']:
+    return 0
+  elif partition_name in ['1', 'b']:
+    return 1
+
   return None
 
 
@@ -219,12 +317,12 @@
     if len(fields) >= 4 and fields[3] == quotedname:
       assert fields[0].startswith('mtd')
       assert fields[0].endswith(':')
-      return '%s%d' % (F['MTD_PREFIX'], int(fields[0][3:-1]))
+      return '%s%d' % (GetMtdPrefix(), int(fields[0][3:-1]))
   return None  # no match
 
 
 def IsMtdNand(mtddevname):
-  mtddevname = re.sub(r'^' + F['MTD_PREFIX'], 'mtd', mtddevname)
+  mtddevname = re.sub(r'^' + GetMtdPrefix(), 'mtd', mtddevname)
   path = F['SYSCLASSMTD'] + '/{0}/type'.format(mtddevname)
   data = open(path).read()
   return 'nand' in data
@@ -275,7 +373,8 @@
   Returns:
     Device file of named partition
   """
-  cmd = [SGDISK, '-p', blk_dev]
+  # Note: Android doesn't support '-p' option, need to use '--print'
+  cmd = [SGDISK, '--print', blk_dev]
   devnull = open('/dev/null', 'w')
   try:
     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=devnull)
@@ -641,6 +740,24 @@
   return True
 
 
+def GetOsFromManifest(manifest):
+  """Determine which OS (FiberOS, Android) the image is for from the manifest.
+
+  Args:
+    manifest: the manifest from an image file
+
+  Returns:
+    'android' if any Android specific image name is found in the manifest,
+    otherwise it returns 'fiberos' (default).
+
+  """
+  for key in manifest.keys():
+    if key.endswith('-sha1'):
+      if key[:-5] in ANDROID_IMAGES:
+        return 'android'
+  return 'fiberos'
+
+
 class ProgressBar(object):
   """Progress bar that prints one dot per 1MB."""
 
@@ -695,26 +812,37 @@
     Log('W: psback/logos unavailable for tracing.\n')
 
 
-def GetPartition(opt):
-  """Return the partiton to install to, given the command line options."""
-  if opt.partition == 'other':
-    boot = GetBootedPartition()
+def GetPartition(partition_name, target_os):
+  """Return the partition to install to.
+
+  Args:
+    partition_name: partition name from command-line
+                    {'primary', 'secondary', 'other'}
+    target_os: 'fiberos' or 'android'
+
+  Returns:
+    0 or 1
+
+  Raises:
+    Fatal: if no partition could be determined
+  """
+  if partition_name == 'other':
+    if target_os == GetOs():
+      boot = GetBootedPartition()
+    else:
+      boot = GetActivePartitionFromHNVRAM(target_os)
     assert boot in [None, 0, 1]
     if boot is None:
       # Policy decision: if we're booted from NFS, install to secondary
       return 1
     else:
       return boot ^ 1
-  elif opt.partition in ['primary', 0]:
+  elif partition_name in ['primary', 0]:
     return 0
-  elif opt.partition in ['secondary', 1]:
+  elif partition_name in ['secondary', 1]:
     return 1
-  elif opt.partition:
-    raise Fatal('--partition must be one of: primary, secondary, other')
-  elif opt.tar:
-    raise Fatal('A --partition option must be provided with --tar')
   else:
-    return None
+    raise Fatal('--partition must be one of: primary, secondary, other')
 
 
 def InstallKernel(kern, partition):
@@ -729,7 +857,7 @@
 
   partition_name = 'kernel%d' % partition
   mtd = GetMtdDevForNameOrNone(partition_name)
-  gpt = GetGptPartitionForName(F['MMCBLK0'], partition_name)
+  gpt = GetGptPartitionForName(GetMmcblk0Prefix(), partition_name)
   if mtd:
     VerbosePrint('Writing kernel to %r\n' % mtd)
     InstallToMtd(kern, mtd)
@@ -759,7 +887,7 @@
       if gpt:
         mtd = None
   else:
-    gpt = GetGptPartitionForName(F['MMCBLK0'], partition_name)
+    gpt = GetGptPartitionForName(GetMmcblk0Prefix(), partition_name)
   if mtd:
     if GetPlatform().startswith('GFMN'):
       VerbosePrint('Writing rootfs to %r\n' % mtd)
@@ -775,6 +903,72 @@
     raise Fatal('no partition named %r is available' % partition_name)
 
 
+def InstallAndroidBoot(boot, partition):
+  """Install an Android boot.img file.
+
+  Args:
+    boot: a FileWithSecureHash object.
+    partition: the partition to install to, 0 or 1.
+
+  Raises:
+    Fatal: if install fails
+  """
+
+  partition_name = ANDROID_BOOT_PARTITIONS[partition]
+  gpt = GetGptPartitionForName(GetMmcblk0Prefix(), partition_name)
+  if gpt:
+    VerbosePrint('Writing boot.img to %r\n' % gpt)
+    InstallToFile(boot, gpt)
+  else:
+    raise Fatal('no partition named %r is available' % partition_name)
+
+
+def InstallAndroidSystem(system, partition):
+  """Install an Android system.img file.
+
+  Args:
+    system: a FileWithSecureHash object.
+    partition: the partition to install to, 0 or 1.
+
+  Raises:
+    Fatal: if install fails
+  """
+
+  partition_name = ANDROID_SYSTEM_PARTITIONS[partition]
+  gpt = GetGptPartitionForName(GetMmcblk0Prefix(), partition_name)
+  if gpt:
+    VerbosePrint('Writing system.img.raw to %r\n' % gpt)
+    InstallToFile(system, gpt)
+  else:
+    raise Fatal('no partition named %r is available' % partition_name)
+
+
+def InstallAndroidBsu(bsu):
+  """Install an Android BSU file.
+
+  Args:
+    bsu: a FileWithSecureHash object.
+
+  Raises:
+    Fatal: if install fails
+  """
+
+  is_bsu_current = False
+  gpt = GetGptPartitionForName(GetMmcblk0Prefix(), ANDROID_BSU_PARTITION)
+  if gpt:
+    with open(gpt, 'rb') as gptfile:
+      VerbosePrint('Checking if android_bsu is up to date.\n')
+      is_bsu_current = IsIdentical('android_bsu', bsu.filelike, gptfile)
+    if is_bsu_current:
+      VerbosePrint('android_bsu is the latest.\n')
+    else:
+      bsu.filelike.seek(0, os.SEEK_SET)
+      VerbosePrint('Writing android_bsu.elf to %r\n' % gpt)
+      InstallToFile(bsu, gpt)
+  else:
+    raise Fatal('no partition named %r is available' % ANDROID_BSU_PARTITION)
+
+
 def UnlockMMC(mmc_name):
   if mmc_name in MMC_RO_LOCK:
     with open(MMC_RO_LOCK[mmc_name], 'w') as f:
@@ -805,7 +999,11 @@
       WriteLoaderToMtd(loader, loader_start, mtd, 'loader')
       installed = True
   # For hd254 we also write the loader to the emmc boot partitions.
-  for emmc_name in ['MMCBLK0BOOT0', 'MMCBLK0BOOT1']:
+  if GetOs() == 'android':
+    emmc_list = ['MMCBLK0BOOT0-ANDROID', 'MMCBLK0BOOT1-ANDROID']
+  else:
+    emmc_list = ['MMCBLK0BOOT0', 'MMCBLK0BOOT1']
+  for emmc_name in emmc_list:
     emmc_dev = F[emmc_name]
     if os.path.exists(emmc_dev):
       UnlockMMC(emmc_name)
@@ -843,19 +1041,23 @@
     WriteLoaderToMtd(uloader, uloader_start, mtd, 'uloader')
 
 
-def InstallImage(f, partition, skiploader=False, skiploadersig=False):
+def InstallImage(opt):
   """Install an image.
 
   Args:
-    f: a file-like objected expected to provide a stream in tar format
-    partition: integer 0 or 1 of the partition to install into
-    skiploader: skip installation of a bootloader
-    skiploadersig: skip checking of bootloader signature
+    opt: command-line options
 
+  Returns:
+    0 for success, else an error code
   Raises:
     Fatal: if install fails
   """
 
+  if not opt.partition:
+    # default to the safe option if not given
+    opt.partition = 'other'
+
+  f = OpenPathOrUrl(opt.tar)
   tar = tarfile.open(mode='r|*', fileobj=f)
   first = tar.next()
 
@@ -879,13 +1081,16 @@
   CheckMinimumVersion(manifest)
   CheckMisc(manifest)
 
+  target_os = GetOsFromManifest(manifest)
+  partition = GetPartition(opt.partition, target_os)
+
   loader_bin_list = ['loader.img', 'loader.bin']
   loader_sig_list = ['loader.sig']
   if CheckMultiLoader(manifest):
     loader_bin_list = ['loader.%s.bin' % GetPlatform().lower()]
     loader_sig_list = ['loader.%s.sig' % GetPlatform().lower()]
 
-  uloader = loader = None
+  uloader = loader = android_bsu = None
   uloadersig = FileWithSecureHash(StringIO.StringIO(''), 'badsig')
 
   # TODO(cgibson): Modern ginstall images contain a loadersig. However, some
@@ -907,11 +1112,29 @@
       # already processed
       pass
     elif ti.name in ['kernel.img', 'vmlinuz', 'vmlinux', 'uImage']:
-      fh = FileWithSecureHash(tar.extractfile(ti), secure_hash)
-      InstallKernel(fh, partition)
+      if target_os != 'fiberos':
+        VerbosePrint('Cannot install kernel img in Android!\n')
+      else:
+        fh = FileWithSecureHash(tar.extractfile(ti), secure_hash)
+        InstallKernel(fh, partition)
     elif ti.name.startswith('rootfs.'):
-      fh = FileWithSecureHash(tar.extractfile(ti), secure_hash)
-      InstallRootfs(fh, partition)
+      if target_os != 'fiberos':
+        VerbosePrint('Cannot install rootfs img in Android!\n')
+      else:
+        fh = FileWithSecureHash(tar.extractfile(ti), secure_hash)
+        InstallRootfs(fh, partition)
+    elif ti.name == 'boot.img':
+      if target_os != 'android':
+        VerbosePrint('Cannot install boot img in FiberOS!\n')
+      else:
+        fh = FileWithSecureHash(tar.extractfile(ti), secure_hash)
+        InstallAndroidBoot(fh, partition)
+    elif ti.name == 'system.img.raw':
+      if target_os != 'android':
+        VerbosePrint('Cannot install system img in FiberOS!\n')
+      else:
+        fh = FileWithSecureHash(tar.extractfile(ti), secure_hash)
+        InstallAndroidSystem(fh, partition)
     elif ti.name in loader_bin_list:
       buf = StringIO.StringIO(tar.extractfile(ti).read())
       loader = FileWithSecureHash(buf, secure_hash)
@@ -924,34 +1147,49 @@
     elif ti.name == 'uloader.sig':
       buf = StringIO.StringIO(tar.extractfile(ti).read())
       uloadersig = FileWithSecureHash(buf, secure_hash)
+    elif ti.name == 'android_bsu.elf':
+      buf = StringIO.StringIO(tar.extractfile(ti).read())
+      android_bsu = FileWithSecureHash(buf, secure_hash)
     else:
       print 'Unknown install file %s' % ti.name
 
-  if skiploadersig:
+  if opt.skiploadersig:
     loadersig = uloadersig = None
 
   key = GetKey()
-  if loadersig and loader and not skiploader:
+  if loadersig and loader and not opt.skiploader:
     if not Verify(loader.filelike, loadersig.filelike, key):
       raise Fatal('Loader signing check failed.')
     loader.filelike.seek(0, os.SEEK_SET)
-  if uloadersig and uloader and not skiploader:
+  if uloadersig and uloader and not opt.skiploader:
     if not Verify(uloader.filelike, uloadersig.filelike, key):
       raise Fatal('Uloader signing check failed.')
     uloader.filelike.seek(0, os.SEEK_SET)
 
   if loader:
-    if skiploader:
+    if opt.skiploader:
       VerbosePrint('Skipping loader installation.\n')
     else:
       InstallLoader(loader)
 
   if uloader:
-    if skiploader:
+    if opt.skiploader:
       VerbosePrint('Skipping uloader installation.\n')
     else:
       InstallUloader(uloader)
 
+  if android_bsu:
+    if opt.skiploader:
+      VerbosePrint('Skipping android_bsu installation.\n')
+    else:
+      InstallAndroidBsu(android_bsu)
+
+  if SetBootPartition(target_os, partition) != 0:
+    VerbosePrint('Unable to set boot partition\n')
+    return HNVRAM_ERR
+
+  return 0
+
 
 def OpenPathOrUrl(path):
   """Try to open path as a URL and as a local file."""
@@ -980,6 +1218,16 @@
   if not (opt.drm or opt.tar or opt.partition):
     o.fatal('Expected at least one of --partition, --tar, or --drm')
 
+  # handle 'ginstall -p <partition>' separately
+  if not opt.drm and not opt.tar:
+    partition = GetPartition(opt, GetOs())
+    if SetBootPartition(GetOs(), partition) != 0:
+      VerbosePrint('Unable to set boot partition\n')
+      return HNVRAM_ERR
+    return 0
+
+  # from here: ginstall [-t <tarfile>] [--drm <blob>] [options...]
+
   quiet = opt.quiet
 
   if opt.basepath:
@@ -989,21 +1237,11 @@
   if opt.drm:
     WriteDrm(opt)
 
-  if opt.tar and not opt.partition:
-    # default to the safe option if not given
-    opt.partition = 'other'
-
-  partition = GetPartition(opt)
+  ret = 0
   if opt.tar:
-    f = OpenPathOrUrl(opt.tar)
-    InstallImage(f, partition, skiploader=opt.skiploader,
-                 skiploadersig=opt.skiploadersig)
+    ret = InstallImage(opt)
 
-  if partition is not None and SetBootPartition(partition) != 0:
-    VerbosePrint('Unable to set boot partition\n')
-    return HNVRAM_ERR
-
-  return 0
+  return ret
 
 
 def BroadcomDeviceIsSecure():
diff --git a/ginstall/ginstall_test.py b/ginstall/ginstall_test.py
index 07cb294..5b335ea 100755
--- a/ginstall/ginstall_test.py
+++ b/ginstall/ginstall_test.py
@@ -40,13 +40,16 @@
 
   def setUp(self):
     self.tmpdir = tempfile.mkdtemp()
+    self.hnvram_dir = self.tmpdir + '/hnvram'
     self.script_out = self.tmpdir + '/out'
     self.old_path = os.environ['PATH']
     self.old_bufsize = ginstall.BUFSIZE
     self.old_files = ginstall.F
+    os.environ['GINSTALL_HNVRAM_DIR'] = self.hnvram_dir
     os.environ['GINSTALL_OUT_FILE'] = self.script_out
     os.environ['GINSTALL_TEST_FAIL'] = ''
     os.environ['PATH'] = 'testdata/bin:' + self.old_path
+    os.makedirs(self.hnvram_dir)
     os.makedirs(self.tmpdir + '/dev')
     ginstall.F['ETCPLATFORM'] = 'testdata/etc/platform'
     ginstall.F['DEV'] = self.tmpdir + '/dev'
@@ -69,6 +72,9 @@
     ginstall.MMC_RO_LOCK['MMCBLK0BOOT1'] = (
         self.tmpdir + '/mmcblk0boot1/force_ro')
 
+    # default OS to 'fiberos'
+    self.WriteOsFile('fiberos')
+
   def tearDown(self):
     os.environ['PATH'] = self.old_path
     shutil.rmtree(self.tmpdir, ignore_errors=True)
@@ -80,6 +86,23 @@
     open(filename, 'w').write(version)
     ginstall.F['ETCVERSION'] = filename
 
+  def WriteOsFile(self, os_name):
+    """Create a fake /etc/os file in /tmp."""
+    filename = self.tmpdir + '/os'
+    open(filename, 'w').write(os_name)
+    ginstall.F['ETCOS'] = filename
+
+  def WriteHnvramAttr(self, attr, val):
+    filename = self.hnvram_dir + '/%s' % attr
+    open(filename, 'w').write(val)
+
+  def ReadHnvramAttr(self, attr):
+    filename = self.hnvram_dir + '/%s' % attr
+    try:
+      return open(filename).read()
+    except IOError:
+      return None
+
   def testVerify(self):
     self.assertTrue(ginstall.Verify(
         open('testdata/img/loader.bin'),
@@ -158,11 +181,44 @@
                       origfile, 'mtd0.tmp')
 
   def testSetBootPartition(self):
-    ginstall.SetBootPartition(0)
-    ginstall.SetBootPartition(1)
+    self.WriteOsFile('fiberos')
+    ginstall.SetBootPartition('fiberos', 0)
+    self.assertEqual('kernel0', self.ReadHnvramAttr('ACTIVATED_KERNEL_NAME'))
+    ginstall.SetBootPartition('fiberos', 1)
+    self.assertEqual('kernel1', self.ReadHnvramAttr('ACTIVATED_KERNEL_NAME'))
+    ginstall.SetBootPartition('android', 0)
+    self.assertEqual('a', self.ReadHnvramAttr('ANDROID_ACTIVE_PARTITION'))
+    self.assertEqual('android', self.ReadHnvramAttr('BOOT_TARGET'))
+    ginstall.SetBootPartition('android', 1)
+    self.assertEqual('b', self.ReadHnvramAttr('ANDROID_ACTIVE_PARTITION'))
+    self.assertEqual('android', self.ReadHnvramAttr('BOOT_TARGET'))
+
+    self.WriteOsFile('android')
+    ginstall.SetBootPartition('fiberos', 0)
+    self.assertEqual('kernel0', self.ReadHnvramAttr('ACTIVATED_KERNEL_NAME'))
+    self.assertEqual('fiberos', self.ReadHnvramAttr('BOOT_TARGET'))
+    ginstall.SetBootPartition('fiberos', 1)
+    self.assertEqual('kernel1', self.ReadHnvramAttr('ACTIVATED_KERNEL_NAME'))
+    self.assertEqual('fiberos', self.ReadHnvramAttr('BOOT_TARGET'))
+    ginstall.SetBootPartition('android', 0)
+    self.assertEqual('a', self.ReadHnvramAttr('ANDROID_ACTIVE_PARTITION'))
+    ginstall.SetBootPartition('android', 1)
+    self.assertEqual('b', self.ReadHnvramAttr('ANDROID_ACTIVE_PARTITION'))
+
+    # also verify the hnvram command history for good measures
     out = open(self.script_out).read().splitlines()
     self.assertEqual(out[0], 'hnvram -q -w ACTIVATED_KERNEL_NAME=kernel0')
     self.assertEqual(out[1], 'hnvram -q -w ACTIVATED_KERNEL_NAME=kernel1')
+    self.assertEqual(out[2], 'hnvram -q -w ANDROID_ACTIVE_PARTITION=a')
+    self.assertEqual(out[3], 'hnvram -q -w BOOT_TARGET=android')
+    self.assertEqual(out[4], 'hnvram -q -w ANDROID_ACTIVE_PARTITION=b')
+    self.assertEqual(out[5], 'hnvram -q -w BOOT_TARGET=android')
+    self.assertEqual(out[6], 'hnvram -q -w ACTIVATED_KERNEL_NAME=kernel0')
+    self.assertEqual(out[7], 'hnvram -q -w BOOT_TARGET=fiberos')
+    self.assertEqual(out[8], 'hnvram -q -w ACTIVATED_KERNEL_NAME=kernel1')
+    self.assertEqual(out[9], 'hnvram -q -w BOOT_TARGET=fiberos')
+    self.assertEqual(out[10], 'hnvram -q -w ANDROID_ACTIVE_PARTITION=a')
+    self.assertEqual(out[11], 'hnvram -q -w ANDROID_ACTIVE_PARTITION=b')
 
   def testParseManifest(self):
     l = ('installer_version: 99\nimage_type: fake\n'
@@ -189,6 +245,34 @@
     manifest = ginstall.ParseManifest(in_f)
     self.assertTrue(ginstall.CheckPlatform(manifest))
 
+  def testGetOs(self):
+    self.WriteOsFile('fiberos')
+    self.assertEqual('fiberos', ginstall.GetOs())
+    self.WriteOsFile('android')
+    self.assertEqual('android', ginstall.GetOs())
+    # in case file doesn't exist, default is 'fiberos'
+    os.remove(self.tmpdir + '/os')
+    self.assertEqual('fiberos', ginstall.GetOs())
+
+  def testGetMtdPrefix(self):
+    self.WriteOsFile('fiberos')
+    self.assertEqual(ginstall.F['MTD_PREFIX'], ginstall.GetMtdPrefix())
+    self.WriteOsFile('android')
+    self.assertEqual(ginstall.F['MTD_PREFIX-ANDROID'], ginstall.GetMtdPrefix())
+    # unknown OS returns 'fiberos'
+    self.WriteOsFile('windows')
+    self.assertEqual(ginstall.F['MTD_PREFIX'], ginstall.GetMtdPrefix())
+
+  def testGetMmcblk0Prefix(self):
+    self.WriteOsFile('fiberos')
+    self.assertEqual(ginstall.F['MMCBLK0'], ginstall.GetMmcblk0Prefix())
+    self.WriteOsFile('android')
+    self.assertEqual(ginstall.F['MMCBLK0-ANDROID'],
+                     ginstall.GetMmcblk0Prefix())
+    # unknown OS returns 'fiberos'
+    self.WriteOsFile('windows')
+    self.assertEqual(ginstall.F['MMCBLK0'], ginstall.GetMmcblk0Prefix())
+
   def testGetInternalHarddisk(self):
     self.assertEqual(ginstall.GetInternalHarddisk(), None)
 
@@ -268,20 +352,141 @@
       manifest = {'version': v}
       self.assertRaises(ginstall.Fatal, ginstall.CheckMisc, manifest)
 
-  def testGetBootedFromCmdLine(self):
-    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline1'
+  def MakeManifestWithFilenameSha1s(self, filename):
+    m = ('installer_version: 4\n'
+         'image_type: unlocked\n'
+         'version: gftv254-48-pre2-1100-g25ff8d0-ck\n'
+         'platforms: [ GFHD254 ]\n')
+    if filename is not None:
+      m += '%s-sha1: 9b5236c282b8c11b38a630361b6c690d6aaa50cb\n' % filename
+
+    in_f = StringIO.StringIO(m)
+    return ginstall.ParseManifest(in_f)
+
+  def testGetOsFromManifest(self):
+    # android specific image names return 'android'
+    for img in ginstall.ANDROID_IMAGES:
+      manifest = self.MakeManifestWithFilenameSha1s(img)
+      self.assertEqual('android', ginstall.GetOsFromManifest(manifest))
+
+    # fiberos image names or anything non-android returns 'fiberos'
+    for img in ['rootfs.img', 'kernel.img', 'whatever.img']:
+      manifest = self.MakeManifestWithFilenameSha1s(img)
+      self.assertEqual('fiberos', ginstall.GetOsFromManifest(manifest))
+
+    # no sha1 entry in the manifest returns 'fiberos'
+    manifest = self.MakeManifestWithFilenameSha1s(None)
+    self.assertEqual('fiberos', ginstall.GetOsFromManifest(manifest))
+
+  def testGetBootedPartition(self):
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.none'
+    self.assertEqual(None, ginstall.GetBootedPartition())
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.0'
+    self.assertEqual(0, ginstall.GetBootedPartition())
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.1'
+    self.assertEqual(1, ginstall.GetBootedPartition())
+
+    # Android
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.android.none'
+    self.assertEqual(None, ginstall.GetBootedPartition())
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.android.0'
+    self.assertEqual(0, ginstall.GetBootedPartition())
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.android.1'
+    self.assertEqual(1, ginstall.GetBootedPartition())
+
+    # Prowl
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.none'
     self.assertEqual(ginstall.GetBootedPartition(), None)
-    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline2'
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.0'
     self.assertEqual(ginstall.GetBootedPartition(), 0)
-    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline3'
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.1'
     self.assertEqual(ginstall.GetBootedPartition(), 1)
 
+  def testGetActivePartitionFromHNVRAM(self):
+    # FiberOS looks at ACTIVATED_KERNEL_NAME, not ANDROID_ACTIVE_PARTITION
+    # 0
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', '0')
+    self.assertEqual(0, ginstall.GetActivePartitionFromHNVRAM('fiberos'))
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', '0')
+    self.assertEqual(0, ginstall.GetActivePartitionFromHNVRAM('fiberos'))
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', '1')
+    self.assertEqual(0, ginstall.GetActivePartitionFromHNVRAM('fiberos'))
+    # 1
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', '1')
+    self.assertEqual(1, ginstall.GetActivePartitionFromHNVRAM('fiberos'))
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', '0')
+    self.assertEqual(1, ginstall.GetActivePartitionFromHNVRAM('fiberos'))
+
+    # Android looks at ANDROID_ACTIVE_PARTITION, not ACTIVATED_KERNEL_NAME
+    # 0
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', '0')
+    self.assertEqual(0, ginstall.GetActivePartitionFromHNVRAM('android'))
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', '0')
+    self.assertEqual(0, ginstall.GetActivePartitionFromHNVRAM('android'))
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', '1')
+    self.assertEqual(0, ginstall.GetActivePartitionFromHNVRAM('android'))
+    # 1
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', '1')
+    self.assertEqual(1, ginstall.GetActivePartitionFromHNVRAM('android'))
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', '0')
+    self.assertEqual(1, ginstall.GetActivePartitionFromHNVRAM('android'))
+
+  def TestGetPartition(self):
+    self.assertEqual(0, ginstall.GetPartition('primary', 'fiberos'))
+    self.assertEqual(0, ginstall.GetPartition(0, 'fiberos'))
+    self.assertEqual(1, ginstall.GetPartition('secondary', 'fiberos'))
+    self.assertEqual(1, ginstall.GetPartition(1, 'fiberos'))
+    self.assertEqual(0, ginstall.GetPartition('primary', 'android'))
+    self.assertEqual(0, ginstall.GetPartition(0, 'android'))
+    self.assertEqual(1, ginstall.GetPartition('secondary', 'android'))
+    self.assertEqual(1, ginstall.GetPartition(1, 'android'))
+
+    # other: FiberOS->FiberOS
+    self.WriteOsFile('fiberos')
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.none'
+    self.assertEqual(1, ginstall.GetPartition('other', 'fiberos'))
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.0'
+    self.assertEqual(1, ginstall.GetPartition('other', 'fiberos'))
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.1'
+    self.assertEqual(0, ginstall.GetPartition('other', 'fiberos'))
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.none'
+    self.assertEqual(1, ginstall.GetPartition('other', 'fiberos'))
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.0'
+    self.assertEqual(1, ginstall.GetPartition('other', 'fiberos'))
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.1'
+    self.assertEqual(0, ginstall.GetPartition('other', 'fiberos'))
+
+    # other: FiberOS->Android
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', 'a')
+    self.assertEqual(1, ginstall.GetPartition('other', 'android'))
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', 'b')
+    self.assertEqual(0, ginstall.GetPartition('other', 'android'))
+    self.WriteHnvramAttr('ANDROID_ACTIVE_PARTITION', 'bla')
+    self.assertEqual(1, ginstall.GetPartition('other', 'android'))
+
+    # other: Android->FiberOS
+    self.WriteOsFile('android')
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', '0')
+    self.assertEqual(1, ginstall.GetPartition('other', 'fiberos'))
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', '1')
+    self.assertEqual(0, ginstall.GetPartition('other', 'fiberos'))
+    self.WriteHnvramAttr('ACTIVATED_KERNEL_NAME', 'bla')
+    self.assertEqual(1, ginstall.GetPartition('other', 'fiberos'))
+
+    # other: Android->Android
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.android.none'
+    self.assertEqual(1, ginstall.GetPartition('other', 'android'))
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.android.0'
+    self.assertEqual(1, ginstall.GetPartition('other', 'android'))
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.android.1'
+    self.assertEqual(0, ginstall.GetPartition('other', 'android'))
+
     # Test prowl and gfactive
-    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline4'
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.none'
     self.assertEqual(ginstall.GetBootedPartition(), None)
-    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline5'
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.0'
     self.assertEqual(ginstall.GetBootedPartition(), 0)
-    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline6'
+    ginstall.F['PROC_CMDLINE'] = 'testdata/proc/cmdline.prowl.1'
     self.assertEqual(ginstall.GetBootedPartition(), 1)
 
   def testUloaderSigned(self):
diff --git a/ginstall/install_test.sh b/ginstall/install_test.sh
index ec372c5..910971d 100755
--- a/ginstall/install_test.sh
+++ b/ginstall/install_test.sh
@@ -4,20 +4,29 @@
 
 tmpdir="$(mktemp -d)"
 export PATH="$tmpdir/bin:${PATH}"
+export GINSTALL_HNVRAM_DIR="$tmpdir/hnvram"
 export GINSTALL_OUT_FILE="$tmpdir/out"
+export GINSTALL_PLATFORM_FILE="$tmpdir/etc/platform"
 psiz=$(stat --format=%s testdata/img/loader.gflt110.bin)
 lsiz=$(stat --format=%s testdata/img/loader.img)
 ksiz=$(stat --format=%s testdata/img/kernel.img)
 rsiz=$(stat --format=%s testdata/img/rootfs.img)
 usiz=$(stat --format=%s testdata/img/uloader.img)
+bsiz=$(stat --format=%s testdata/img/boot.img)
+ssiz=$(stat --format=%s testdata/img/system.img.raw)
+asiz=$(stat --format=%s testdata/img/android_bsu.elf)
 testdata/bin/http_server "$tmpdir/http_ctrl" &
 
 setup_fakeroot() {
   platform="$1"
+  running_os="fiberos"
+  [ $# -gt 1 ] && running_os="$2"
   rm -f "$GINSTALL_OUT_FILE"
   rm -rf "$tmpdir/*"
   mkdir -p "$tmpdir/bin" "$tmpdir/dev" "$tmpdir/etc"
+  mkdir -p "$tmpdir/dev/block" "$tmpdir/dev/mtd"
   mkdir -p "$tmpdir/sys/block/sda"
+  mkdir -p "$GINSTALL_HNVRAM_DIR"
   cp -r testdata/bin "$tmpdir"
   cp -r testdata/proc "$tmpdir"
   cp -r testdata/img "$tmpdir"
@@ -30,10 +39,18 @@
   echo 0123456789abcdef0123456789abcdef >"$tmpdir/dev/mtd3"
   echo 0123456789abcdef0123456789abcdef >"$tmpdir/dev/mtd4"
 
+  # write a pre-existing android_bsu
+  echo 0123456789abcdef0123456789abcdef >"$tmpdir/dev/mmcblk0p2"
+  echo 0123456789abcdef0123456789abcdef >"$tmpdir/dev/block/mmcblk0p2"
+
   for i in {5..31}; do touch "$tmpdir/dev/mtd$i"; done
 
+  # duplicate /dev/mtd* to /dev/mtd/mtd* (used by Android)
+  cp ${tmpdir}/dev/mtd[0-9]* ${tmpdir}/dev/mtd/
+
   cp "testdata/proc/mtd.$platform" "$tmpdir/proc/mtd"
   echo "$platform" >"$tmpdir/etc/platform"
+  echo "$running_os" >"$tmpdir/etc/os"
   echo 0123456789abcdef0123456789abcdef >"$tmpdir/etc/gfiber_public.der"
 }
 
@@ -150,6 +167,74 @@
 
 
 
+# kernel in NAND, raw no bbt
+# rootfs on eMMC
+# (GFHD254)
+echo; echo; echo "GFHD254 (fiberos->fiberos)"
+setup_fakeroot GFHD254 fiberos
+expected="\
+psback
+logos ginstall
+flash_erase --quiet ${tmpdir}/dev/mtd0 0 0
+hnvram -q -w ACTIVATED_KERNEL_NAME=kernel1"
+
+WVPASS ./ginstall.py --basepath="$tmpdir" --tar=./testdata/img/image_v4.gi --partition=secondary --skiploadersig
+WVPASSEQ "$expected" "$(cat $GINSTALL_OUT_FILE)"
+WVPASS cmp --bytes="$lsiz" "${tmpdir}/dev/mtd0" testdata/img/loader.img
+WVPASS cmp --bytes="$ksiz" "${tmpdir}/dev/mmcblk0p14" testdata/img/kernel.img
+WVPASS cmp --bytes="$rsiz" "${tmpdir}/dev/mmcblk0p15" testdata/img/rootfs.img
+
+# FiberOS->Android (GFHD254)
+echo; echo; echo "GFHD254 (fiberos->android)"
+setup_fakeroot GFHD254 fiberos
+expected="\
+psback
+logos ginstall
+flash_erase --quiet ${tmpdir}/dev/mtd0 0 0
+hnvram -q -w ANDROID_ACTIVE_PARTITION=b
+hnvram -q -w BOOT_TARGET=android"
+
+WVPASS ./ginstall.py --basepath="$tmpdir" --tar=./testdata/img/image_android_v4.gi --partition=secondary --skiploadersig
+WVPASSEQ "$expected" "$(cat $GINSTALL_OUT_FILE)"
+WVPASS cmp --bytes="$lsiz" "${tmpdir}/dev/mtd0" testdata/img/loader.img
+WVPASS cmp --bytes="$asiz" "${tmpdir}/dev/mmcblk0p2" testdata/img/android_bsu.elf
+WVPASS cmp --bytes="$bsiz" "${tmpdir}/dev/mmcblk0p6" testdata/img/boot.img
+WVPASS cmp --bytes="$ssiz" "${tmpdir}/dev/mmcblk0p10" testdata/img/system.img.raw
+
+# Android->Android (GFHD254)
+echo; echo; echo "GFHD254 (android->android)"
+setup_fakeroot GFHD254 android
+expected="\
+psback
+logos ginstall
+flash_erase --quiet ${tmpdir}/dev/mtd/mtd0 0 0
+hnvram -q -w ANDROID_ACTIVE_PARTITION=a"
+
+WVPASS ./ginstall.py --basepath="$tmpdir" --tar=./testdata/img/image_android_v4.gi --partition=primary --skiploadersig
+WVPASSEQ "$expected" "$(cat $GINSTALL_OUT_FILE)"
+WVPASS cmp --bytes="$lsiz" "${tmpdir}/dev/mtd/mtd0" testdata/img/loader.img
+WVPASS cmp --bytes="$asiz" "${tmpdir}/dev/block/mmcblk0p2" testdata/img/android_bsu.elf
+WVPASS cmp --bytes="$bsiz" "${tmpdir}/dev/block/mmcblk0p5" testdata/img/boot.img
+WVPASS cmp --bytes="$ssiz" "${tmpdir}/dev/block/mmcblk0p9" testdata/img/system.img.raw
+
+# Android->FiberOS (GFHD254)
+echo; echo; echo "GFHD254 (android->fiberos)"
+setup_fakeroot GFHD254 android
+expected="\
+psback
+logos ginstall
+flash_erase --quiet ${tmpdir}/dev/mtd/mtd0 0 0
+hnvram -q -w ACTIVATED_KERNEL_NAME=kernel0
+hnvram -q -w BOOT_TARGET=fiberos"
+
+WVPASS ./ginstall.py --basepath="$tmpdir" --tar=./testdata/img/image_v4.gi --partition=primary --skiploadersig
+WVPASSEQ "$expected" "$(cat $GINSTALL_OUT_FILE)"
+WVPASS cmp --bytes="$lsiz" "${tmpdir}/dev/mtd/mtd0" testdata/img/loader.img
+WVPASS cmp --bytes="$ksiz" "${tmpdir}/dev/block/mmcblk0p12" testdata/img/kernel.img
+WVPASS cmp --bytes="$rsiz" "${tmpdir}/dev/block/mmcblk0p13" testdata/img/rootfs.img
+
+
+
 # kernel in NOR, raw
 # rootfs in NOR, raw
 # loader in NOR, raw
diff --git a/ginstall/testdata/bin/hnvram b/ginstall/testdata/bin/hnvram
deleted file mode 120000
index 3c2bde7..0000000
--- a/ginstall/testdata/bin/hnvram
+++ /dev/null
@@ -1 +0,0 @@
-write_args_to_file
\ No newline at end of file
diff --git a/ginstall/testdata/bin/hnvram b/ginstall/testdata/bin/hnvram
new file mode 100755
index 0000000..6faa2b6
--- /dev/null
+++ b/ginstall/testdata/bin/hnvram
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# log into out file
+exe=$(basename "$0")
+echo "$exe" $* >> "$GINSTALL_OUT_FILE"
+
+# simple cmdline parser
+for i in "$@"; do
+  if [ "$i" == "-q" ]; then
+    continue
+  elif [ "$i" == "-r" ]; then
+    read=1
+  elif [ "$i" == "-w" ]; then
+    write=1
+  else
+    attr_val="$i"
+  fi
+done
+
+IFS='=' read attr val <<< "$attr_val"
+
+GINSTALL_ATTR_FILE="${GINSTALL_HNVRAM_DIR}/${attr}"
+
+if [ -n "$write" ]; then
+  echo -n "$val" > "$GINSTALL_ATTR_FILE"
+elif [ -n "$read" ]; then
+  if [ ! -r "$GINSTALL_ATTR_FILE" ]; then
+    exit 1
+  else
+    cat "$GINSTALL_ATTR_FILE"
+  fi
+fi
+
+if [ ! -z "$GINSTALL_TEST_FAIL" ]; then
+  exit 1
+fi
+
+exit 0
diff --git a/ginstall/testdata/bin/sgdisk b/ginstall/testdata/bin/sgdisk
index 69c640f..7e9392a 100755
--- a/ginstall/testdata/bin/sgdisk
+++ b/ginstall/testdata/bin/sgdisk
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-echo "Disk /dev/mmcblk0: 7634944 sectors, 3.6 GiB
+default="Disk /dev/mmcblk0: 7634944 sectors, 3.6 GiB
 Logical sector size: 512 bytes
 Disk identifier (GUID): FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
 Partition table holds up to 128 entries
@@ -15,3 +15,37 @@
   19          954368         1740799   384.0 MiB   FFFF  rootfs1
   20         1740800         1871871   64.0 MiB    FFFF  emergency
   21         1871872         7114751   2.5 GiB     8300  data+ext4"
+
+gfhd254="Disk /dev/mmcblk0: 61071360 sectors, 29.1 GiB
+Logical sector size: 512 bytes
+Disk identifier (GUID): BE51F469-09A6-4A05-9A3B-D09855D3E5A1
+Partition table holds up to 128 entries
+First usable sector is 34, last usable sector is 61071326
+Partitions will be aligned on 2-sector boundaries
+Total free space is 0 sectors (0 bytes)
+
+Number  Start (sector)    End (sector)  Size       Code  Name
+   1              34             161   64.0 KiB    8300  nvram
+   2             162             673   256.0 KiB   8300  bsu
+   3             674            2721   1024.0 KiB  8300  misc
+   4            2722            4769   1024.0 KiB  8300  hwcfg
+   5            4770           70305   32.0 MiB    8300  boot_a
+   6           70306          135841   32.0 MiB    8300  boot_b
+   7          135842          152225   8.0 MiB     8300  metadata
+   8          152226          156321   2.0 MiB     8300  eio
+   9          156322         4350625   2.0 GiB     8300  system_a
+  10         4350626         8544929   2.0 GiB     8300  system_b
+  11         8544930         8547873   1.4 MiB     8300  hnvram
+  12         8547874         8613409   32.0 MiB    8300  kernel0
+  13         8613410         9399841   384.0 MiB   8300  rootfs0
+  14         9399842         9465377   32.0 MiB    8300  kernel1
+  15         9465378        10251809   384.0 MiB   8300  rootfs1
+  16        10251810        10382881   64.0 MiB    8300  emergency
+  17        10382882        61071326   24.2 GiB    8300  userdata"
+
+platform="$(cat $GINSTALL_PLATFORM_FILE)"
+if [ "$platform"  == "GFHD254" ]; then
+  echo "$gfhd254"
+else
+  echo "$default"
+fi
diff --git a/ginstall/testdata/img/MANIFEST b/ginstall/testdata/img/MANIFEST
index de8c93b..5e839f4 100644
--- a/ginstall/testdata/img/MANIFEST
+++ b/ginstall/testdata/img/MANIFEST
@@ -1,7 +1,7 @@
 installer_version: 4
 image_type: unittest
 version: gftv200-40.1
-platforms: [ GFUNITTEST, GFSC100, GFMN100, GFHD200, GFHD100, GFMS100, GFRG210, GFRG200 ]
+platforms: [ GFUNITTEST, GFSC100, GFMN100, GFHD200, GFHD254, GFHD100, GFMS100, GFRG210, GFRG200 ]
 loader.img-sha1: 228d83f86ba967704a94afce92e1e4cbba3b24a6
 uloader.img-sha1: 171e9a2e524c1f3a64f43ef7d254b85e764f1096
 kernel.img-sha1: fbd0ad4af43303c6b3001920689db2eb4d5212d0
diff --git a/ginstall/testdata/img/android_bsu.elf b/ginstall/testdata/img/android_bsu.elf
new file mode 100644
index 0000000..44687e7
--- /dev/null
+++ b/ginstall/testdata/img/android_bsu.elf
@@ -0,0 +1 @@
+android_bsu.elf
\ No newline at end of file
diff --git a/ginstall/testdata/img/boot.img b/ginstall/testdata/img/boot.img
new file mode 100644
index 0000000..7afe48a
--- /dev/null
+++ b/ginstall/testdata/img/boot.img
@@ -0,0 +1 @@
+boot.img
\ No newline at end of file
diff --git a/ginstall/testdata/img/image_android_v4.gi b/ginstall/testdata/img/image_android_v4.gi
new file mode 100644
index 0000000..601138a
--- /dev/null
+++ b/ginstall/testdata/img/image_android_v4.gi
Binary files differ
diff --git a/ginstall/testdata/img/image_v4.gi b/ginstall/testdata/img/image_v4.gi
index 11aac14..7d66211 100644
--- a/ginstall/testdata/img/image_v4.gi
+++ b/ginstall/testdata/img/image_v4.gi
Binary files differ
diff --git a/ginstall/testdata/img/system.img.raw b/ginstall/testdata/img/system.img.raw
new file mode 100644
index 0000000..3fcfe41
--- /dev/null
+++ b/ginstall/testdata/img/system.img.raw
@@ -0,0 +1 @@
+system.img.raw
\ No newline at end of file
diff --git a/ginstall/testdata/proc/cmdline2 b/ginstall/testdata/proc/cmdline.0
similarity index 100%
rename from ginstall/testdata/proc/cmdline2
rename to ginstall/testdata/proc/cmdline.0
diff --git a/ginstall/testdata/proc/cmdline3 b/ginstall/testdata/proc/cmdline.1
similarity index 100%
rename from ginstall/testdata/proc/cmdline3
rename to ginstall/testdata/proc/cmdline.1
diff --git a/ginstall/testdata/proc/cmdline.android.0 b/ginstall/testdata/proc/cmdline.android.0
new file mode 100644
index 0000000..8fb7943
--- /dev/null
+++ b/ginstall/testdata/proc/cmdline.android.0
@@ -0,0 +1 @@
+mem=1024m@0m mem=1024m@2048m bmem=336m@672m bmem=256m@2048m brcm_cma=768m@2304m ramoops.mem_address=0x3F800000 ramoops.mem_size=0x800000 ramoops.console_size=0x400000 pmem=8m@1016m androidboot.selinux=permissive androidboot.hardware=gfhd254 androidboot.gfiber_system_img=system_a bootreason=main_chip_input
diff --git a/ginstall/testdata/proc/cmdline.android.1 b/ginstall/testdata/proc/cmdline.android.1
new file mode 100644
index 0000000..4da7328
--- /dev/null
+++ b/ginstall/testdata/proc/cmdline.android.1
@@ -0,0 +1 @@
+mem=1024m@0m mem=1024m@2048m bmem=336m@672m bmem=256m@2048m brcm_cma=768m@2304m ramoops.mem_address=0x3F800000 ramoops.mem_size=0x800000 ramoops.console_size=0x400000 pmem=8m@1016m androidboot.selinux=permissive androidboot.hardware=gfhd254 bootreason=main_chip_input androidboot.gfiber_system_img=system_b
diff --git a/ginstall/testdata/proc/cmdline.android.none b/ginstall/testdata/proc/cmdline.android.none
new file mode 100644
index 0000000..9fb507c
--- /dev/null
+++ b/ginstall/testdata/proc/cmdline.android.none
@@ -0,0 +1 @@
+mem=1024m@0m mem=1024m@2048m bmem=336m@672m bmem=256m@2048m brcm_cma=768m@2304m ramoops.mem_address=0x3F800000 ramoops.mem_size=0x800000 ramoops.console_size=0x400000 pmem=8m@1016m androidboot.selinux=permissive androidboot.hardware=gfhd254 bootreason=main_chip_input
diff --git a/ginstall/testdata/proc/cmdline1 b/ginstall/testdata/proc/cmdline.none
similarity index 100%
rename from ginstall/testdata/proc/cmdline1
rename to ginstall/testdata/proc/cmdline.none
diff --git a/ginstall/testdata/proc/cmdline5 b/ginstall/testdata/proc/cmdline.prowl.0
similarity index 100%
rename from ginstall/testdata/proc/cmdline5
rename to ginstall/testdata/proc/cmdline.prowl.0
diff --git a/ginstall/testdata/proc/cmdline6 b/ginstall/testdata/proc/cmdline.prowl.1
similarity index 100%
rename from ginstall/testdata/proc/cmdline6
rename to ginstall/testdata/proc/cmdline.prowl.1
diff --git a/ginstall/testdata/proc/cmdline4 b/ginstall/testdata/proc/cmdline.prowl.none
similarity index 100%
rename from ginstall/testdata/proc/cmdline4
rename to ginstall/testdata/proc/cmdline.prowl.none
diff --git a/ginstall/testdata/proc/mtd.GFHD254 b/ginstall/testdata/proc/mtd.GFHD254
new file mode 100644
index 0000000..21616d1
--- /dev/null
+++ b/ginstall/testdata/proc/mtd.GFHD254
@@ -0,0 +1,3 @@
+dev:    size   erasesize  name
+mtd0: 00400000 00001000 "flash0.bolt"
+mtd1: 00400000 00001000 "flash0"