Add 'partitions_offset_begin' attribute.

This attribute can set the target disk partitions offset
begin size. It is good for protecting some read-only data.

With this attribute, it makes security to protect SoC
read-only bootloader. To ensure that system boot security,
the SoC read-only bootloader has special format that MUST
use the SoC tool to flash it.

Change-Id: I5102f11ab37e1f706b2a1774ff888c29226a115f
Signed-off-by: Weiwen Chen <cww@rock-chips.com>
diff --git a/README b/README
index 32c56c4..6566b1c 100644
--- a/README
+++ b/README
@@ -63,6 +63,10 @@
  disk_size:      The size of the target disk to use, in bytes. This has
                  no default value.
 
+ partitions_offset_begin :
+                 The size of the disk partitions offset begin, in bytes.
+                 Default is 0.
+
  disk_alignment: The alignment, in bytes, to use when laying out
                  partitions. Default is 4096.
 
diff --git a/bpt_unittest.py b/bpt_unittest.py
index 57a58ba..cf9b4f2 100755
--- a/bpt_unittest.py
+++ b/bpt_unittest.py
@@ -279,6 +279,7 @@
   def _MakeTable(self, input_file_names,
                  expected_json_file_name,
                  expected_gpt_file_name=None,
+                 partitions_offset_begin=None,
                  disk_size=None,
                  disk_alignment=None,
                  disk_guid=None,
@@ -293,6 +294,8 @@
       expected_json_file_name: File name of the file with expected JSON output.
       expected_gpt_file_name: File name of the file with expected binary
                               output or None.
+      partitions_offset_begin: if not None, the size of the disk
+                               partitions offset begin to use.
       disk_size: if not None, the size of the disk to use.
       disk_alignment: if not None, the disk alignment to use.
       disk_guid: if not None, the disk GUID to use.
@@ -308,6 +311,7 @@
     bpt = bpttool.Bpt()
     (json_str, gpt_bin) = bpt.make_table(
         inputs,
+        partitions_offset_begin=partitions_offset_begin,
         disk_size=disk_size,
         disk_alignment=disk_alignment,
         disk_guid=disk_guid,
@@ -346,6 +350,14 @@
                     disk_size=bpttool.ParseSize('10 GiB'),
                     disk_alignment=1048576)
 
+  def testPartitionsOffsetBegin(self):
+    """Checks that disk partitions offset begin
+       can be changed on the command-line."""
+    self._MakeTable(['test/base.bpt'],
+                    'test/expected_json_partitions_offset_begin.bpt',
+                    partitions_offset_begin=bpttool.ParseSize('1 MiB'),
+                    disk_size=bpttool.ParseSize('10 GiB'))
+
   def testDiskGuid(self):
     """Checks that disk GUID can be changed on the command-line."""
     self._MakeTable(['test/base.bpt'],
diff --git a/bpttool b/bpttool
index 85f3244..cd2b8d5 100755
--- a/bpttool
+++ b/bpttool
@@ -37,6 +37,7 @@
 JSON_KEYWORD_SETTINGS_DISK_SIZE = 'disk_size'
 JSON_KEYWORD_SETTINGS_DISK_ALIGNMENT = 'disk_alignment'
 JSON_KEYWORD_SETTINGS_DISK_GUID = 'disk_guid'
+JSON_KEYWORD_SETTINGS_PARTITIONS_OFFSET_BEGIN = 'partitions_offset_begin'
 JSON_KEYWORD_PARTITIONS = 'partitions'
 JSON_KEYWORD_PARTITIONS_LABEL = 'label'
 JSON_KEYWORD_PARTITIONS_OFFSET = 'offset'
@@ -807,6 +808,8 @@
   Attributes:
     ab_suffixes: A list of A/B suffixes to use.
     disk_size: An integer with the disk size in bytes.
+    partitions_offset_begin: An integer with the disk partitions
+                             offset begin size in bytes.
     disk_alignment: The alignment to use for partitions.
     disk_guid: The GUID to use for the disk or None or 'auto' if
                automatically generated.
@@ -816,6 +819,7 @@
     """Initializer with defaults."""
     self.ab_suffixes = ['_a', '_b']
     self.disk_size = None
+    self.partitions_offset_begin = 0
     self.disk_alignment = 4096
     self.disk_guid = JSON_KEYWORD_AUTO
 
@@ -887,6 +891,10 @@
         disk_size = sobj.get(JSON_KEYWORD_SETTINGS_DISK_SIZE)
         if disk_size:
           settings.disk_size = ParseSize(disk_size)
+        partitions_offset_begin = sobj.get(
+                JSON_KEYWORD_SETTINGS_PARTITIONS_OFFSET_BEGIN)
+        if partitions_offset_begin:
+          settings.partitions_offset_begin = ParseSize(partitions_offset_begin)
         disk_alignment = sobj.get(JSON_KEYWORD_SETTINGS_DISK_ALIGNMENT)
         if disk_alignment:
           settings.disk_alignment = ParseSize(disk_alignment)
@@ -945,12 +953,14 @@
     ret = ('{{\n'
            '  "' + JSON_KEYWORD_SETTINGS + '": {{\n'
            '    "' + JSON_KEYWORD_SETTINGS_AB_SUFFIXES + '": {},\n'
+           '    "' + JSON_KEYWORD_SETTINGS_PARTITIONS_OFFSET_BEGIN + '": {},\n'
            '    "' + JSON_KEYWORD_SETTINGS_DISK_SIZE + '": {},\n'
            '    "' + JSON_KEYWORD_SETTINGS_DISK_ALIGNMENT + '": {},\n'
            '    "' + JSON_KEYWORD_SETTINGS_DISK_GUID + '": "{}"\n'
            '  }},\n'
            '  "' + JSON_KEYWORD_PARTITIONS + '": [\n').format(
                suffixes_str,
+               settings.partitions_offset_begin,
                settings.disk_size,
                settings.disk_alignment,
                settings.disk_guid)
@@ -1176,6 +1186,7 @@
   def make_table(self,
                  inputs,
                  ab_suffixes=None,
+                 partitions_offset_begin=None,
                  disk_size=None,
                  disk_alignment=None,
                  disk_guid=None,
@@ -1190,6 +1201,8 @@
       inputs: List of JSON files to parse.
       ab_suffixes: List of the A/B suffixes (as a comma-separated string)
                    to use or None to not override.
+      partitions_offset_begin: Size of disk partitions offset
+                               begin or None to not override.
       disk_size: Size of disk or None to not override.
       disk_alignment: Disk alignment or None to not override.
       disk_guid: Disk GUID as a string or None to not override.
@@ -1211,6 +1224,8 @@
       settings.disk_size = int(math.ceil(disk_size))
     if disk_alignment:
       settings.disk_alignment = int(disk_alignment)
+    if partitions_offset_begin:
+      settings.partitions_offset_begin = int(partitions_offset_begin)
     if ab_suffixes:
       settings.ab_suffixes = ab_suffixes.split(',')
     if disk_guid:
@@ -1234,6 +1249,19 @@
           'Disk alignment size of {} is not divisible by {}.\n'.format(
               settings.disk_alignment, DISK_SECTOR_SIZE))
 
+    if settings.partitions_offset_begin != 0:
+      # Disk partitions offset begin size must be
+      # divisible by disk sector size.
+      if settings.partitions_offset_begin % settings.disk_alignment != 0:
+        raise BptError(
+            'Disk Partitions offset begin size of {} '
+            'is not divisible by {}.\n'.format(
+                settings.partitions_offset_begin, settings.disk_alignment))
+      settings.partitions_offset_begin = max(settings.partitions_offset_begin,
+                                           DISK_SECTOR_SIZE*(1 + GPT_NUM_LBAS))
+      settings.partitions_offset_begin = RoundToMultiple(
+          settings.partitions_offset_begin, settings.disk_alignment)
+
     # Expand A/B partitions and skip ignored partitions.
     expanded_partitions = []
     for p in partitions:
@@ -1271,7 +1299,9 @@
     # support more in the future by splitting up the available bytes
     # between them.
     grow_part = None
-    offset = DISK_SECTOR_SIZE*(1 + GPT_NUM_LBAS)
+    # offset minimal size: DISK_SECTOR_SIZE*(1 + GPT_NUM_LBAS)
+    offset = max(settings.partitions_offset_begin,
+                 DISK_SECTOR_SIZE*(1 + GPT_NUM_LBAS))
     for p in partitions:
       if p.grow:
         if grow_part:
@@ -1309,7 +1339,9 @@
 
     # Now we can assign partition start offsets for all partitions,
     # including the grow partition.
-    offset = DISK_SECTOR_SIZE*(1 + GPT_NUM_LBAS)
+    # offset minimal size: DISK_SECTOR_SIZE*(1 + GPT_NUM_LBAS)
+    offset = max(settings.partitions_offset_begin,
+                 DISK_SECTOR_SIZE*(1 + GPT_NUM_LBAS))
     for p in partitions:
       # Align offset.
       offset = RoundToMultiple(offset, settings.disk_alignment)
@@ -1462,6 +1494,10 @@
                             action='append')
     sub_parser.add_argument('--ab_suffixes',
                             help='Set or override A/B suffixes.')
+    sub_parser.add_argument('--partitions_offset_begin',
+                            help='Set or override disk partitions '
+                                 'offset begin size.',
+                            type=ParseSize)
     sub_parser.add_argument('--disk_size',
                             help='Set or override disk size.',
                             type=ParseSize)
@@ -1553,6 +1589,7 @@
 
     try:
       (json_str, gpt_bin) = self.bpt.make_table(args.input, args.ab_suffixes,
+                                                args.partitions_offset_begin,
                                                 args.disk_size,
                                                 args.disk_alignment,
                                                 args.disk_guid)
diff --git a/test/expected_json_alignment.bpt b/test/expected_json_alignment.bpt
index e1a13bf..1996359 100644
--- a/test/expected_json_alignment.bpt
+++ b/test/expected_json_alignment.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 1048576,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_base.bpt b/test/expected_json_base.bpt
index a496c2a..189684a 100644
--- a/test/expected_json_base.bpt
+++ b/test/expected_json_base.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_disk_guid.bpt b/test/expected_json_disk_guid.bpt
index bbbf447..82265e7 100644
--- a/test/expected_json_disk_guid.bpt
+++ b/test/expected_json_disk_guid.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-00000000002a"
diff --git a/test/expected_json_partitions_offset_begin.bpt b/test/expected_json_partitions_offset_begin.bpt
new file mode 100644
index 0000000..bb66b19
--- /dev/null
+++ b/test/expected_json_partitions_offset_begin.bpt
@@ -0,0 +1,102 @@
+{
+  "settings": {
+    "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 1048576,
+    "disk_size": 10737418240,
+    "disk_alignment": 4096,
+    "disk_guid": "01234567-89ab-cdef-0123-000000000000"
+  },
+  "partitions": [
+    {
+      "label": "boot_a",
+      "offset": 1048576,
+      "size": 33554432,
+      "grow": false,
+      "guid": "01234567-89ab-cdef-0123-000000000001",
+      "type_guid": "bb499290-b57e-49f6-bf41-190386693794",
+      "flags": "0x0000000000000000",
+      "ignore": false,
+      "ab": true,
+      "ab_expanded": true,
+      "position": 0
+    },
+    {
+      "label": "boot_b",
+      "offset": 34603008,
+      "size": 33554432,
+      "grow": false,
+      "guid": "01234567-89ab-cdef-0123-000000000002",
+      "type_guid": "bb499290-b57e-49f6-bf41-190386693794",
+      "flags": "0x0000000000000000",
+      "ignore": false,
+      "ab": true,
+      "ab_expanded": true,
+      "position": 0
+    },
+    {
+      "label": "system_a",
+      "offset": 68157440,
+      "size": 536870912,
+      "grow": false,
+      "guid": "01234567-89ab-cdef-0123-000000000003",
+      "type_guid": "0f2778c4-5cc1-4300-8670-6c88b7e57ed6",
+      "flags": "0x0000000000000000",
+      "ignore": false,
+      "ab": true,
+      "ab_expanded": true,
+      "position": 0
+    },
+    {
+      "label": "system_b",
+      "offset": 605028352,
+      "size": 536870912,
+      "grow": false,
+      "guid": "01234567-89ab-cdef-0123-000000000004",
+      "type_guid": "0f2778c4-5cc1-4300-8670-6c88b7e57ed6",
+      "flags": "0x0000000000000000",
+      "ignore": false,
+      "ab": true,
+      "ab_expanded": true,
+      "position": 0
+    },
+    {
+      "label": "odm_a",
+      "offset": 1141899264,
+      "size": 1073741824,
+      "grow": false,
+      "guid": "01234567-89ab-cdef-0123-000000000005",
+      "type_guid": "e99d84d7-2c1b-44cf-8c58-effae2dc2558",
+      "flags": "0x0000000000000000",
+      "ignore": false,
+      "ab": true,
+      "ab_expanded": true,
+      "position": 0
+    },
+    {
+      "label": "odm_b",
+      "offset": 2215641088,
+      "size": 1073741824,
+      "grow": false,
+      "guid": "01234567-89ab-cdef-0123-000000000006",
+      "type_guid": "e99d84d7-2c1b-44cf-8c58-effae2dc2558",
+      "flags": "0x0000000000000000",
+      "ignore": false,
+      "ab": true,
+      "ab_expanded": true,
+      "position": 0
+    },
+    {
+      "label": "userdata",
+      "offset": 3289382912,
+      "size": 7448014848,
+      "grow": true,
+      "guid": "01234567-89ab-cdef-0123-000000000007",
+      "type_guid": "0bb7e6ed-4424-49c0-9372-7fbab465ab4c",
+      "flags": "0x0000000000000000",
+      "ignore": false,
+      "ab": false,
+      "ab_expanded": false,
+      "position": 0
+    }
+  ]
+}
diff --git a/test/expected_json_size.bpt b/test/expected_json_size.bpt
index 189dc9c..b72452f 100644
--- a/test/expected_json_size.bpt
+++ b/test/expected_json_size.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 21474836480,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_change_ab_size.bpt b/test/expected_json_stacked_change_ab_size.bpt
index c16b533..be02d8f 100644
--- a/test/expected_json_stacked_change_ab_size.bpt
+++ b/test/expected_json_stacked_change_ab_size.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_change_flags.bpt b/test/expected_json_stacked_change_flags.bpt
index 12c5cdc..0ae8461 100644
--- a/test/expected_json_stacked_change_flags.bpt
+++ b/test/expected_json_stacked_change_flags.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_ignore.bpt b/test/expected_json_stacked_ignore.bpt
index 5683b07..858162a 100644
--- a/test/expected_json_stacked_ignore.bpt
+++ b/test/expected_json_stacked_ignore.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_new_partition.bpt b/test/expected_json_stacked_new_partition.bpt
index 83636e1..0ca2443 100644
--- a/test/expected_json_stacked_new_partition.bpt
+++ b/test/expected_json_stacked_new_partition.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_new_partition_on_top.bpt b/test/expected_json_stacked_new_partition_on_top.bpt
index 9f6efae..79c3fdc 100644
--- a/test/expected_json_stacked_new_partition_on_top.bpt
+++ b/test/expected_json_stacked_new_partition_on_top.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_override_settings.bpt b/test/expected_json_stacked_override_settings.bpt
index 2d8979c..d1c7b7d 100644
--- a/test/expected_json_stacked_override_settings.bpt
+++ b/test/expected_json_stacked_override_settings.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["-0", "-1"],
+    "partitions_offset_begin": 0,
     "disk_size": 16106127360,
     "disk_alignment": 512,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_positions.bpt b/test/expected_json_stacked_positions.bpt
index 8a173cc..0c11be3 100644
--- a/test/expected_json_stacked_positions.bpt
+++ b/test/expected_json_stacked_positions.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_stacked_size.bpt b/test/expected_json_stacked_size.bpt
index 06095d1..eb089da 100644
--- a/test/expected_json_stacked_size.bpt
+++ b/test/expected_json_stacked_size.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["_a", "_b"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"
diff --git a/test/expected_json_suffixes.bpt b/test/expected_json_suffixes.bpt
index 8155077..1e15d5c 100644
--- a/test/expected_json_suffixes.bpt
+++ b/test/expected_json_suffixes.bpt
@@ -1,6 +1,7 @@
 {
   "settings": {
     "ab_suffixes": ["-A", "-B"],
+    "partitions_offset_begin": 0,
     "disk_size": 10737418240,
     "disk_alignment": 4096,
     "disk_guid": "01234567-89ab-cdef-0123-000000000000"