build_library: Create MiniOS disk layout with two partitions.

Add two miniOS partitions. One in the beginning and one at the end
after stateful partition. The partition in the beginning has priority.
Add a new feature `back_partition` to the cgpt script to be able to
to place MiniOS -B behind the expanded stateful partition at the
end of the disk.

       start        size    part  contents
           0           1          PMBR (Boot GUID:)
           1           1          Pri GPT header
           2          32          Pri GPT table
     5349376     8401041       1  Label: "STATE"
                                  Type: Linux data
                                  UUID: 31694155-F9E7-404C-8179-C5BEA9C8BC83
      131141       65536       2  Label: "KERN-A"
                                  Type: ChromeOS kernel
                                  UUID: 5AE5238C-2648-FD47-AF92-6D566F3320E5
                                  Attr: priority=15 tries=15 successful=0
      434176     4915200       3  Label: "ROOT-A"
                                  Type: ChromeOS rootfs
                                  UUID: 411C7DB2-DC13-464A-99B0-000DBB004AE0
                              ...
          64      131072      13  Label: "MiniOS-A"
                                  Type: ChromeOS kernel
                                  UUID: D3C1DE80-998A-054C-97F7-6A43727C90F2
                                  Attr: priority=15 tries=5 successful=0
    13750417      131072      14  Label: "MiniOS-B"
                                  Type: ChromeOS kernel
                                  UUID: C24FF886-BAB9-7342-9C8A-F42CDB4591F7
                                  Attr: priority=0 tries=0 successful=0
BUG=b:174913800
TEST=flash and chromeos-install on hatch

Change-Id: I2d45248b7cf88c5f20d31f33d0fe8df30197b5a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosutils/+/2565271
Tested-by: Vyshu Khota <vyshu@google.com>
Commit-Queue: Vyshu Khota <vyshu@google.com>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Sarthak Kukreti <sarthakkukreti@chromium.org>
diff --git a/build_library/README.disk_layout b/build_library/README.disk_layout
index 9352a3b..cc2c990 100644
--- a/build_library/README.disk_layout
+++ b/build_library/README.disk_layout
@@ -107,7 +107,7 @@
         # (block_size * fs_blocks / 1024 / 1024 == MiB).
         "fs_size": "2048 MiB",
 
-        # Special handling of this partition. Expand is only supported value.
+        # Special handling of this partition.
         "features":["expand"],
 
         # Optional, default 'random'. Explicitly define the partition uuid.
@@ -207,6 +207,7 @@
 
 Features values:
   expand
+  last_partition
 
 
 
@@ -220,8 +221,9 @@
     images to be exactly large enough to hold files.
 
     On installation, its size will be expanded to use all available space
-    on the target drive. If it does not contain a valid file system, it will
-    be formatted to ext4 on system boot.
+    on the target drive, leaving room for a 'last_partition' if it exists.
+    If it does not contain a valid file system, it will be formatted on
+    system boot.
 
   Partition 2 - Kernel Slot A
     Contains the recovery kernel on recovery images. Contains the standard
@@ -259,6 +261,14 @@
     installation/update don't detect a secure bios (ie: ChromeOS hardware), then
     GRUB/UBoot, or other bios boot files/kernels will be installed here.
 
+  Partition 13 - MiniOS A (src/platform2/minios)
+    Partition at the beginning of disk. MiniOS A will have priority over
+    MiniOS B when booting.
+
+  Partition 14 - MiniOS B
+    Has feature 'last_partition' which ensures that it is placed at the
+    end of the disk.
+
 
 Inheritance Rules:
 
diff --git a/build_library/cgpt.py b/build_library/cgpt.py
index 1316abe..e4b2e1f 100755
--- a/build_library/cgpt.py
+++ b/build_library/cgpt.py
@@ -357,7 +357,7 @@
       'size', 'fs_size', 'fs_options', 'erase_block_size', 'hybrid_mbr',
       'reserved_erase_blocks', 'max_bad_erase_blocks', 'external_gpt',
       'page_size', 'size_min', 'fs_size_min'))
-  valid_features = set(('expand',))
+  valid_features = set(('expand', 'last_partition'))
 
   config = _LoadStackedPartitionConfig(filename)
   try:
@@ -541,6 +541,7 @@
   ret = {
       'expand_count': 0,
       'expand_min': 0,
+      'last_partition_count': 0,
       'byte_count': start_sector,
   }
 
@@ -559,6 +560,8 @@
       ret['expand_min'] += partition['bytes']
     else:
       ret['byte_count'] += partition['bytes']
+    if 'last_partition' in partition['features']:
+      ret['last_partition_count'] += 1
 
   # Account for the secondary GPT header and table.
   ret['byte_count'] += SECONDARY_GPT_BYTES
@@ -570,6 +573,11 @@
     raise InvalidLayout('1 expand partition allowed, %d requested'
                         % ret['expand_count'])
 
+  # Only one partition can be last on the disk.
+  if ret['last_partition_count'] > 1:
+    raise InvalidLayout('Only one last partition allowed, %d requested'
+                        % ret['last_partition_count'])
+
   # We lose some extra bytes from the alignment which are now not considered in
   # min_disk_size because partitions are aligned on the fly. Adding
   # fs_block_align_losses corrects for the loss.
@@ -805,6 +813,7 @@
 
   metadata = GetMetadataPartition(partitions)
   stateful = None
+  last_part = None
   # Set up the expanding partition size and write out all the cgpt add
   # commands.
   for partition in partitions:
@@ -816,6 +825,11 @@
       stateful = partition
       continue
 
+    # Save the last partition to place at the end of the disk..
+    if 'last_partition' in partition['features']:
+      last_part = partition
+      continue
+
     if (partition.get('type') in ['data', 'rootfs'] and partition['bytes'] > 1):
       lines += fs_align_snippet
 
@@ -842,7 +856,24 @@
     lines += fs_align_snippet + [
         'blocks=$(( numsecs - (curr + %d) / block_size ))' %
         SECONDARY_GPT_BYTES,
+    ]
+    if last_part is not None:
+      lines += [
+          'reserved_blocks=$(( (%s + block_size - 1) / block_size ))'
+          % last_part['var'],
+          ': $(( blocks = blocks - reserved_blocks ))',
+      ]
+    lines += [
         gpt_add % (stateful['num'], stateful['type'], stateful['label']),
+        ': $(( curr += blocks * block_size ))',
+    ]
+
+  if last_part is not None:
+    lines += [
+        'reserved_blocks=$(( (%s + block_size - 1) / block_size ))'
+        % last_part['var'],
+        'blocks=$((reserved_blocks))',
+        gpt_add % (last_part['num'], last_part['type'], last_part['label']),
     ]
 
   # Set default priorities and retry counter on kernel partitions.
diff --git a/build_library/minios_disk_layout.json b/build_library/minios_disk_layout.json
new file mode 100644
index 0000000..564cc9e
--- /dev/null
+++ b/build_library/minios_disk_layout.json
@@ -0,0 +1,30 @@
+{
+  # For MiniOS testing purposes only. Do not use.
+  "parent": "disk_layout_v2.json",
+  "metadata": {
+    "block_size": 512,
+    "fs_block_size": 4096,
+    "fs_align": "2 MiB"
+  },
+  "layouts": {
+    # common is the standard layout template.
+    "common": [
+      {
+        # MiniOS A partition.
+        "num": 13,
+        "label": "MiniOS-A",
+        "type": "kernel",
+        "size": "64 MiB"
+      },
+      {
+        # MiniOS B partition.
+        "num": 14,
+        "label": "MiniOS-B",
+        "type": "kernel",
+        "size": "64 MiB",
+        # Places partition at the end of disk.
+        "features": ["last_partition"]
+      }
+    ]
+  }
+}