Import parse-dynparts 25/305425/2
authorJacek Kryszyn <j.kryszyn@samsung.com>
Wed, 31 Jan 2024 13:53:14 +0000 (14:53 +0100)
committerJacek Kryszyn <j.kryszyn@samsung.com>
Mon, 12 Feb 2024 12:19:26 +0000 (13:19 +0100)
The project allows to read metadata of a super partition and mount partitions
stored on the super partition using dm-linear. Will be used to implement
the super partition with hal/rootfs dm-linear partitions.
Project url: https://github.com/tchebb/parse-android-dynparts. parse-dynparts
was moved from initrd where it was imported for the first time from
parse-android-dynparts master branch (commit c8837c1). This commit is based
on initrd commit e92dfb3.

Change-Id: I5753df5fd7924fb9d698059406ea322369fc2d7c

29 files changed:
CMakeLists.target-build
CMakeLists.txt
LICENSE.Apache-2.0 [moved from LICENSE with 100% similarity]
LICENSE.MIT [new file with mode: 0644]
data/40-upgrade.list.in
packaging/upgrade.spec
src/parse-dynparts/.clang-format [new file with mode: 0644]
src/parse-dynparts/.gitignore [new file with mode: 0644]
src/parse-dynparts/.vscode/extensions.json [new file with mode: 0644]
src/parse-dynparts/.vscode/settings.json [new file with mode: 0644]
src/parse-dynparts/CMakeLists.txt [new file with mode: 0644]
src/parse-dynparts/LICENSE [new file with mode: 0644]
src/parse-dynparts/README.md [new file with mode: 0644]
src/parse-dynparts/lib.cpp [new file with mode: 0644]
src/parse-dynparts/lib.hpp [new file with mode: 0644]
src/parse-dynparts/liblp/CMakeLists.txt [new file with mode: 0644]
src/parse-dynparts/liblp/README.md [new file with mode: 0644]
src/parse-dynparts/liblp/include/liblp/liblp.h [new file with mode: 0644]
src/parse-dynparts/liblp/include/liblp/metadata_format.h [new file with mode: 0644]
src/parse-dynparts/liblp/reader.cpp [new file with mode: 0644]
src/parse-dynparts/liblp/reader.h [new file with mode: 0644]
src/parse-dynparts/liblp/utility.cpp [new file with mode: 0644]
src/parse-dynparts/liblp/utility.h [new file with mode: 0644]
src/parse-dynparts/main.cpp [new file with mode: 0644]
src/parse-dynparts/test/generate_test_data.sh [new file with mode: 0755]
src/parse-dynparts/test/metadataio.cpp [new file with mode: 0644]
src/parse-dynparts/test/metadataio.h [new file with mode: 0644]
src/parse-dynparts/test/super_dump.cpp [new file with mode: 0644]
src/parse-dynparts/test/test.cpp [new file with mode: 0644]

index 7802192..53b554e 100644 (file)
@@ -24,3 +24,4 @@ ADD_SUBDIRECTORY(src/upgrade-apply-deltafs)
 ADD_SUBDIRECTORY(src/blkid-print)
 ADD_SUBDIRECTORY(data)
 ADD_SUBDIRECTORY(scripts/rw-upgrade)
+ADD_SUBDIRECTORY(src/parse-dynparts)
index e864599..7597586 100644 (file)
 # limitations under the License.
 
 CMAKE_MINIMUM_REQUIRED(VERSION 2.82)
-PROJECT(upgrade C)
+PROJECT(upgrade C CXX)
+
+find_package(GTest REQUIRED)
+enable_testing()
 
 INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/src/)
 
@@ -26,6 +29,7 @@ ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "")
 MESSAGE("Build type: ${CMAKE_BUILD_TYPE}")
 
 SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC -fPIE")
+SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
 SET(CMAKE_C_FLAGS_DEBUG "-O0 -g")
 SET(CMAKE_C_FLAGS_RELEASE "-O2")
 
similarity index 100%
rename from LICENSE
rename to LICENSE.Apache-2.0
diff --git a/LICENSE.MIT b/LICENSE.MIT
new file mode 100644 (file)
index 0000000..80b056d
--- /dev/null
@@ -0,0 +1,9 @@
+The MIT License
+
+Copyright (c) 2022 Samsung Electronics Co., Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
index f927f86..41cb254 100644 (file)
@@ -8,6 +8,7 @@ ${CMAKE_INSTALL_LIBDIR}/libcrypto.so.1.1
 /bin/dd
 /bin/grep
 /bin/blkid-print
+/usr/sbin/parse-dynparts
 "
 
 DIRECTORIES="
index 52563e3..ad2e8c1 100644 (file)
@@ -3,10 +3,10 @@
 
 Name:          upgrade
 Summary:       Upgrade support for Tizen
-Version:       8.0.3
+Version:       8.1.0
 Release:       0
 Group:         System
-License:       Apache-2.0
+License:       MIT, Apache-2.0
 Source0:       %{name}-%{version}.tar.gz
 
 BuildRequires: cmake
@@ -18,8 +18,10 @@ BuildRequires:  pkgconfig(dlog)
 Buildrequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(hal-api-device)
 BuildRequires:  libtar-devel
+BuildRequires:  gtest-devel
 
 Requires:       upgrade-engine = %{version}-%{release}
+Requires:       parse-dynparts = %{version}-%{release}
 
 %description
 Metapackage requiring all upgrade-related packages on the platform.
@@ -41,6 +43,14 @@ upgrade-tools.
 Update engine for updating Tizen platform images using delta files
 generated by upgrade-tools.
 
+%package -n parse-dynparts
+Summary:  Utility needed to parse dynamic partition on-disk format
+
+%description -n parse-dynparts
+This package provides utility needed to parse "super" on-disk format, and print
+out necessary device mapper maps, needed to later mount filesystems contained in
+dynamic partition area.
+
 %prep
 %setup -q
 
@@ -63,10 +73,14 @@ LDFLAGS="$LDFLAGS"
        -DUPGRADE_VAR_DIR=%TZ_SYS_VAR \
        -DUPGRADE_PKGSCRIPTS_DIR=%TZ_SYS_UPGRADE_SCRIPTS \
        -DUNIT_DIR=%{_unitdir} \
+       -DINSTALL_DIR=%{_sbindir} \
        .
 
 make %{?jobs:-j%jobs}
 
+%check
+ctest -V %{?_smp_mflags}
+
 %install
 %make_install
 
@@ -113,11 +127,11 @@ else
 fi
 
 %files
-%license LICENSE
+%license LICENSE.Apache-2.0
 %manifest upgrade.manifest
 
 %files engine
-%license LICENSE
+%license LICENSE.Apache-2.0
 %manifest upgrade.manifest
 %doc README
 %attr(775, root, system_fw) %{fota_dir}
@@ -167,3 +181,8 @@ fi
 %{upgrade_scripts_dir}/update-init.sh
 %{upgrade_scripts_dir}/update-post.sh
 %{upgrade_scripts_dir}/update.sh
+
+%files -n parse-dynparts
+%manifest upgrade.manifest
+%license LICENSE.MIT
+%{_sbindir}/parse-dynparts
\ No newline at end of file
diff --git a/src/parse-dynparts/.clang-format b/src/parse-dynparts/.clang-format
new file mode 100644 (file)
index 0000000..06e3c51
--- /dev/null
@@ -0,0 +1,4 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+...
diff --git a/src/parse-dynparts/.gitignore b/src/parse-dynparts/.gitignore
new file mode 100644 (file)
index 0000000..796b96d
--- /dev/null
@@ -0,0 +1 @@
+/build
diff --git a/src/parse-dynparts/.vscode/extensions.json b/src/parse-dynparts/.vscode/extensions.json
new file mode 100644 (file)
index 0000000..a6a5990
--- /dev/null
@@ -0,0 +1,12 @@
+{
+       // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+       // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+       // List of extensions which should be recommended for users of this workspace.
+       "recommendations": [
+               "ms-vscode.cpptools",
+               "ms-vscode.cmake-tools",
+               "twxs.cmake",
+       ],
+       // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+       "unwantedRecommendations": []
+}
diff --git a/src/parse-dynparts/.vscode/settings.json b/src/parse-dynparts/.vscode/settings.json
new file mode 100644 (file)
index 0000000..add6fba
--- /dev/null
@@ -0,0 +1,4 @@
+{
+       "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
+       "cmake.configureOnOpen": true
+}
diff --git a/src/parse-dynparts/CMakeLists.txt b/src/parse-dynparts/CMakeLists.txt
new file mode 100644 (file)
index 0000000..dfb6e1b
--- /dev/null
@@ -0,0 +1,17 @@
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED True)
+
+add_subdirectory(liblp)
+
+add_executable(parse-dynparts main.cpp lib.cpp)
+target_compile_options(parse-dynparts PRIVATE -Wall -Wextra -pedantic -fPIE)
+
+target_link_libraries(parse-dynparts PRIVATE lp)
+install(TARGETS parse-dynparts DESTINATION ${INSTALL_DIR})
+
+add_executable(parse-dynparts-test test/test.cpp lib.cpp test/metadataio.h test/metadataio.cpp)
+target_compile_options(parse-dynparts-test PRIVATE -Wall -Wextra -pedantic -fPIE -Wno-stringop-truncation)
+target_link_libraries(parse-dynparts-test PRIVATE GTest::gtest_main lp)
+
+include(GoogleTest)
+gtest_discover_tests(parse-dynparts-test)
diff --git a/src/parse-dynparts/LICENSE b/src/parse-dynparts/LICENSE
new file mode 100644 (file)
index 0000000..261eeb9
--- /dev/null
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/src/parse-dynparts/README.md b/src/parse-dynparts/README.md
new file mode 100644 (file)
index 0000000..848b5c9
--- /dev/null
@@ -0,0 +1,88 @@
+Purpose
+=======
+Most devices running Android 10 and higher use Android's [Dynamic Partitions][1]
+feature to allow the different read-only system partitions (e.g. `system`,
+`vendor`, `product`) to share the same pool of storage space. This allows
+vendors to safely resize those partitions in OTA updates, as long as the sum of
+their sizes doesn't exceed that of the physical partition they all reside in.
+
+The physical partition image that holds multiple Android dynamic partitions is
+conventionally named `super.img` and holds similar information as an LVM
+physical volume on Linux: a list of logical partitions, each associated with a
+(possibly non-contiguous) set of blocks in the file that comprise it. Like LVM,
+Android makes use of [Device Mapper's dm-linear target][2] to inform the
+kernel of the logical partitions so it can map them to block devices in
+`/dev/mapper`.
+
+In true Google fashion, however, Android dynamic partitions use a totally custom
+header format that is not compatible with LVM or other similar software. As
+such, the only official tools that exist to mount them are part of Android and
+depend heavily on Android's frameworks, volume manager, and init system. (There
+are [official tools][3] that run on Linux to pack and unpack `super.img` files,
+but they cannot mount them in-place.)
+
+This tool makes it possible to mount `super.img` files with a standard Linux
+userspace. It uses a modified version of Google's AOSP code to parse the
+partition layout, then outputs that layout as a textual "concise device
+specification" which, when passed to `dmsetup`, instructs the kernel to create
+a Device Mapper block device for each logical partition in the image.
+
+[1]: https://source.android.com/devices/tech/ota/dynamic_partitions
+[2]: https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/linear.html
+[3]: https://android.googlesource.com/platform/system/extras/+/master/partition_tools/
+
+Dependencies
+============
+ - CMake
+ - OpenSSL (for hash functions)
+
+Building
+========
+This is a standard C++ CMake project; it builds like any other CMake project.
+For those unfamiliar with CMake, here's the incantation you need to build using
+[Ninja](https://ninja-build.org/) as a backend:
+```
+mkdir build
+cd build
+cmake -G Ninja ..
+ninja
+```
+
+Or, if you don't have Ninja, you can use the Makefile backend:
+```
+mkdir build
+cd build
+cmake ..
+make
+```
+
+Usage
+=====
+
+Setup
+-----
+ 1. Obtain a raw `super.img`. Depending on the source of the image you're
+    working with, this may initially be a sparse image, which you'll have to
+    unsparse using the standard Android `simg2img` tool, or it may be one
+    partition inside a GPT-partitioned disk image.
+ 2. Make your `super.img` available as a loop device (omit `-r` if you want to
+    allow writes):
+    ```
+    losetup -r /dev/loop0 super.img
+    ```
+ 3. Create mappings for the dynamic partitions:
+    ```
+    dmsetup create --concise "$(parse-dynparts /dev/loop0)"
+    ```
+ 4. Access your partitions as `/dev/mapper/dynpart-<NAME>`!
+
+Teardown
+--------
+ 1. Unmap the Device Mapper devices:
+    ```
+    dmsetup remove /dev/mapper/dynpart-*
+    ```
+ 2. Delete the loop device:
+    ```
+    losetup -d /dev/loop0
+    ```
diff --git a/src/parse-dynparts/lib.cpp b/src/parse-dynparts/lib.cpp
new file mode 100644 (file)
index 0000000..1981b05
--- /dev/null
@@ -0,0 +1,74 @@
+/* Copyright (c) 2021 Tom Hebb (https://github.com/tchebb/parse-android-dynparts)
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: Apache-2.0 */
+
+#include "lib.hpp"
+
+#include <iostream>
+#include <sstream>
+#include <string_view>
+
+using namespace android::fs_mgr;
+using std::endl;
+
+std::optional<std::string> go(const android::fs_mgr::LpMetadata &metadata, bool list_tables, std::string_view device_name, std::ostream &messages) {
+  std::string table;
+
+  // Code structure taken from Android's system/core/fs_mgr/fs_mgr_dm_linear.cpp
+  for (const auto& partition : metadata.partitions) {
+    if (!partition.num_extents) {
+      messages << "Skipping zero-length logical partition: "
+               << GetPartitionName(partition) << endl;
+      continue;
+    }
+    if (partition.attributes & LP_PARTITION_ATTR_DISABLED) {
+      messages << "Skipping disabled partition: " << GetPartitionName(partition)
+               << endl;
+      continue;
+    }
+
+    std::ostringstream line;
+
+    if (list_tables)
+      line << GetPartitionName(partition) << " ";
+    else {
+      bool read_only = partition.attributes & LP_PARTITION_ATTR_READONLY;
+      line << GetPartitionName(partition) << ",,," << (read_only ? "ro," : "rw,");
+    }
+
+    uint64_t sector = 0;
+    for (size_t i = 0; i < partition.num_extents; i++) {
+      const auto& extent = metadata.extents[partition.first_extent_index + i];
+      switch (extent.target_type) {
+        case LP_TARGET_TYPE_ZERO:
+          line << sector << " " << extent.num_sectors << " zero";
+          break;
+        case LP_TARGET_TYPE_LINEAR: {
+          if (extent.target_source != 0) {
+            messages << "This utility does not yet support multiple block devices"
+                     << endl;
+            return std::nullopt;
+          }
+
+          if (i && !list_tables) line << ",";
+          if (i && list_tables) line << "\\n";
+
+          line << sector << " " << extent.num_sectors << " linear "
+               << device_name << " " << extent.target_data;
+          break;
+        }
+        default:
+          messages << "Unknown target type in metadata: " << extent.target_type
+                << endl;
+          return std::nullopt;
+      }
+      sector += extent.num_sectors;
+    }
+
+    if (!table.empty() && list_tables) table += "\n";
+    if (!table.empty() && !list_tables) table += ";";
+    table += line.str();
+  }
+
+  return table;
+}
\ No newline at end of file
diff --git a/src/parse-dynparts/lib.hpp b/src/parse-dynparts/lib.hpp
new file mode 100644 (file)
index 0000000..86aa29d
--- /dev/null
@@ -0,0 +1,10 @@
+/* Copyright (c) 2021 Tom Hebb (https://github.com/tchebb/parse-android-dynparts)
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: Apache-2.0 */
+
+#pragma once
+
+#include <optional>
+#include <liblp/liblp.h>
+
+std::optional<std::string> go(const android::fs_mgr::LpMetadata &metadata, bool list_tables, std::string_view device_name, std::ostream &messages);
\ No newline at end of file
diff --git a/src/parse-dynparts/liblp/CMakeLists.txt b/src/parse-dynparts/liblp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..44f3c8a
--- /dev/null
@@ -0,0 +1,7 @@
+add_library(lp STATIC reader.cpp utility.cpp)
+
+target_include_directories(lp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_compile_options(lp PRIVATE -Wall -Wextra -pedantic -fPIE -Wno-stringop-truncation)
+
+find_package(OpenSSL REQUIRED)
+target_link_libraries(lp PRIVATE OpenSSL::Crypto)
\ No newline at end of file
diff --git a/src/parse-dynparts/liblp/README.md b/src/parse-dynparts/liblp/README.md
new file mode 100644 (file)
index 0000000..77d012f
--- /dev/null
@@ -0,0 +1,13 @@
+liblp
+=====
+
+This code is based on `fs_mgr/liblp` from Android's `platform/system/core`
+project. However, I've removed most of the files and edited the ones that remain
+so that they build independently from the rest of the AOSP codebase. All
+functions still declared in the local copy of `include/liblp/liblp.h` ought to
+work. `CMakeLists.txt` is not from upstream and was authored by me to replace
+upstream's `Android.bp`.
+
+Currently derived from the upstream tree at [commit f9c36a2ca632][1].
+
+[1]: https://android.googlesource.com/platform/system/core/+/f9c36a2ca632fa88edba9c2c87f14f2aec1e7fd3/fs_mgr/liblp/
diff --git a/src/parse-dynparts/liblp/include/liblp/liblp.h b/src/parse-dynparts/liblp/include/liblp/liblp.h
new file mode 100644 (file)
index 0000000..14f6ccf
--- /dev/null
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef LIBLP_LIBLP_H
+#define LIBLP_LIBLP_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "metadata_format.h"
+
+namespace android {
+namespace fs_mgr {
+
+// Helper structure for easily interpreting deserialized metadata, or
+// re-serializing metadata.
+struct LpMetadata {
+    LpMetadataGeometry geometry;
+    LpMetadataHeader header;
+    std::vector<LpMetadataPartition> partitions;
+    std::vector<LpMetadataExtent> extents;
+    std::vector<LpMetadataPartitionGroup> groups;
+    std::vector<LpMetadataBlockDevice> block_devices;
+};
+//
+// Read logical partition metadata from its predetermined location on a block
+// device. If readback fails, we also attempt to load from a backup copy.
+std::unique_ptr<LpMetadata> ReadMetadata(const std::string& super_partition, uint32_t slot_number);
+
+// Helper to extract safe C++ strings from partition info.
+std::string GetPartitionName(const LpMetadataPartition& partition);
+std::string GetPartitionGroupName(const LpMetadataPartitionGroup& group);
+std::string GetBlockDevicePartitionName(const LpMetadataBlockDevice& block_device);
+
+// Return the block device that houses the super partition metadata; returns
+// null on failure.
+const LpMetadataBlockDevice* GetMetadataSuperBlockDevice(const LpMetadata& metadata);
+
+// Return the total size of all partitions comprising the super partition.
+uint64_t GetTotalSuperPartitionSize(const LpMetadata& metadata);
+
+// Get the list of block device names required by the given metadata.
+std::vector<std::string> GetBlockDevicePartitionNames(const LpMetadata& metadata);
+
+// Slot suffix helpers.
+uint32_t SlotNumberForSlotSuffix(const std::string& suffix);
+std::string SlotSuffixForSlotNumber(uint32_t slot_number);
+std::string GetPartitionSlotSuffix(const std::string& partition_name);
+
+// Helpers for common functions.
+const LpMetadataPartition* FindPartition(const LpMetadata& metadata, const std::string& name);
+uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition);
+
+}  // namespace fs_mgr
+}  // namespace android
+
+#endif  // LIBLP_LIBLP_H
diff --git a/src/parse-dynparts/liblp/include/liblp/metadata_format.h b/src/parse-dynparts/liblp/include/liblp/metadata_format.h
new file mode 100644 (file)
index 0000000..41d8b0c
--- /dev/null
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOGICAL_PARTITION_METADATA_FORMAT_H_
+#define LOGICAL_PARTITION_METADATA_FORMAT_H_
+
+#ifdef __cplusplus
+#include <string>
+#include <vector>
+#endif
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Magic signature for LpMetadataGeometry. */
+#define LP_METADATA_GEOMETRY_MAGIC 0x616c4467
+
+/* Space reserved for geometry information. */
+#define LP_METADATA_GEOMETRY_SIZE 4096
+
+/* Magic signature for LpMetadataHeader. */
+#define LP_METADATA_HEADER_MAGIC 0x414C5030
+
+/* Current metadata version. */
+#define LP_METADATA_MAJOR_VERSION 10
+#define LP_METADATA_MINOR_VERSION_MIN 0
+#define LP_METADATA_MINOR_VERSION_MAX 2
+
+/* Metadata version needed to use the UPDATED partition attribute. */
+#define LP_METADATA_VERSION_FOR_UPDATED_ATTR 1
+
+/* Metadata version needed for the new expanded header struct. */
+#define LP_METADATA_VERSION_FOR_EXPANDED_HEADER 2
+
+/* Attributes for the LpMetadataPartition::attributes field.
+ *
+ * READONLY - The partition should not be considered writable. When used with
+ * device mapper, the block device will be created as read-only.
+ */
+#define LP_PARTITION_ATTR_NONE 0x0
+#define LP_PARTITION_ATTR_READONLY (1 << 0)
+
+/* This flag is only intended to be used with super_empty.img and super.img on
+ * retrofit devices. On these devices there are A and B super partitions, and
+ * we don't know ahead of time which slot the image will be applied to.
+ *
+ * If set, the partition name needs a slot suffix applied. The slot suffix is
+ * determined by the metadata slot number (0 = _a, 1 = _b).
+ */
+#define LP_PARTITION_ATTR_SLOT_SUFFIXED (1 << 1)
+
+/* This flag is applied automatically when using MetadataBuilder::NewForUpdate.
+ * It signals that the partition was created (or modified) for a snapshot-based
+ * update. If this flag is not present, the partition was likely flashed via
+ * fastboot.
+ */
+#define LP_PARTITION_ATTR_UPDATED (1 << 2)
+
+/* This flag marks a partition as disabled. It should not be used or mapped. */
+#define LP_PARTITION_ATTR_DISABLED (1 << 3)
+
+/* Mask that defines all valid attributes. When changing this, make sure to
+ * update ParseMetadata().
+ */
+#define LP_PARTITION_ATTRIBUTE_MASK_V0 \
+    (LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED)
+#define LP_PARTITION_ATTRIBUTE_MASK_V1 (LP_PARTITION_ATTR_UPDATED | LP_PARTITION_ATTR_DISABLED)
+#define LP_PARTITION_ATTRIBUTE_MASK \
+    (LP_PARTITION_ATTRIBUTE_MASK_V0 | LP_PARTITION_ATTRIBUTE_MASK_V1)
+
+/* Default name of the physical partition that holds logical partition entries.
+ * The layout of this partition will look like:
+ *
+ *     +--------------------+
+ *     | Disk Geometry      |
+ *     +--------------------+
+ *     | Geometry Backup    |
+ *     +--------------------+
+ *     | Metadata           |
+ *     +--------------------+
+ *     | Backup Metadata    |
+ *     +--------------------+
+ *     | Logical Partitions |
+ *     +--------------------+
+ */
+#define LP_METADATA_DEFAULT_PARTITION_NAME "super"
+
+/* Size of a sector is always 512 bytes for compatibility with the Linux kernel. */
+#define LP_SECTOR_SIZE 512
+
+/* Amount of space reserved at the start of every super partition to avoid
+ * creating an accidental boot sector.
+ */
+#define LP_PARTITION_RESERVED_BYTES 4096
+
+/* This structure is stored at block 0 in the first 4096 bytes of the
+ * partition, and again in the following block. It is never modified and
+ * describes how logical partition information can be located.
+ */
+typedef struct LpMetadataGeometry {
+    /*  0: Magic signature (LP_METADATA_GEOMETRY_MAGIC). */
+    uint32_t magic;
+
+    /*  4: Size of the LpMetadataGeometry struct. */
+    uint32_t struct_size;
+
+    /*  8: SHA256 checksum of this struct, with this field set to 0. */
+    uint8_t checksum[32];
+
+    /* 40: Maximum amount of space a single copy of the metadata can use. This
+     * must be a multiple of LP_SECTOR_SIZE.
+     */
+    uint32_t metadata_max_size;
+
+    /* 44: Number of copies of the metadata to keep. For A/B devices, this
+     * will be 2. For an A/B/C device, it would be 3, et cetera. For Non-A/B
+     * it will be 1. A backup copy of each slot is kept, so if this is "2",
+     * there will be four copies total.
+     */
+    uint32_t metadata_slot_count;
+
+    /* 48: Logical block size. This is the minimal alignment for partition and
+     * extent sizes, and it must be a multiple of LP_SECTOR_SIZE. Note that
+     * this must be equal across all LUNs that comprise the super partition,
+     * and thus this field is stored in the geometry, not per-device.
+     */
+    uint32_t logical_block_size;
+} __attribute__((packed)) LpMetadataGeometry;
+
+/* The logical partition metadata has a number of tables; they are described
+ * in the header via the following structure.
+ *
+ * The size of the table can be computed by multiplying entry_size by
+ * num_entries, and the result must not overflow a 32-bit signed integer.
+ */
+typedef struct LpMetadataTableDescriptor {
+    /*  0: Location of the table, relative to end of the metadata header. */
+    uint32_t offset;
+    /*  4: Number of entries in the table. */
+    uint32_t num_entries;
+    /*  8: Size of each entry in the table, in bytes. */
+    uint32_t entry_size;
+} __attribute__((packed)) LpMetadataTableDescriptor;
+
+/* Binary format for the header of the logical partition metadata format.
+ *
+ * The format has three sections. The header must occur first, and the
+ * proceeding tables may be placed in any order after.
+ *
+ *  +-----------------------------------------+
+ *  | Header data - fixed size                |
+ *  +-----------------------------------------+
+ *  | Partition table - variable size         |
+ *  +-----------------------------------------+
+ *  | Partition table extents - variable size |
+ *  +-----------------------------------------+
+ *
+ * The "Header" portion is described by LpMetadataHeader. It will always
+ * precede the other three blocks.
+ *
+ * All fields are stored in little-endian byte order when serialized.
+ *
+ * This struct is versioned; see the |major_version| and |minor_version|
+ * fields.
+ */
+typedef struct LpMetadataHeader {
+    /*  0: Four bytes equal to LP_METADATA_HEADER_MAGIC. */
+    uint32_t magic;
+
+    /*  4: Version number required to read this metadata. If the version is not
+     * equal to the library version, the metadata should be considered
+     * incompatible.
+     */
+    uint16_t major_version;
+
+    /*  6: Minor version. A library supporting newer features should be able to
+     * read metadata with an older minor version. However, an older library
+     * should not support reading metadata if its minor version is higher.
+     */
+    uint16_t minor_version;
+
+    /*  8: The size of this header struct. */
+    uint32_t header_size;
+
+    /* 12: SHA256 checksum of the header, up to |header_size| bytes, computed as
+     * if this field were set to 0.
+     */
+    uint8_t header_checksum[32];
+
+    /* 44: The total size of all tables. This size is contiguous; tables may not
+     * have gaps in between, and they immediately follow the header.
+     */
+    uint32_t tables_size;
+
+    /* 48: SHA256 checksum of all table contents. */
+    uint8_t tables_checksum[32];
+
+    /* 80: Partition table descriptor. */
+    LpMetadataTableDescriptor partitions;
+    /* 92: Extent table descriptor. */
+    LpMetadataTableDescriptor extents;
+    /* 104: Updateable group descriptor. */
+    LpMetadataTableDescriptor groups;
+    /* 116: Block device table. */
+    LpMetadataTableDescriptor block_devices;
+
+    /* Everything past here is header version 1.2+, and is only included if
+     * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must
+     * zero these additional fields.
+     */
+
+    /* 128: See LP_HEADER_FLAG_ constants for possible values. Header flags are
+     * independent of the version number and intended to be informational only.
+     * New flags can be added without bumping the version.
+     */
+    uint32_t flags;
+
+    /* 132: Reserved (zero), pad to 256 bytes. */
+    uint8_t reserved[124];
+} __attribute__((packed)) LpMetadataHeader;
+
+/* This device uses Virtual A/B. Note that on retrofit devices, the expanded
+ * header may not be present.
+ */
+#define LP_HEADER_FLAG_VIRTUAL_AB_DEVICE 0x1
+
+/* This struct defines a logical partition entry, similar to what would be
+ * present in a GUID Partition Table.
+ */
+typedef struct LpMetadataPartition {
+    /*  0: Name of this partition in ASCII characters. Any unused characters in
+     * the buffer must be set to 0. Characters may only be alphanumeric or _.
+     * The name must include at least one ASCII character, and it must be unique
+     * across all partition names. The length (36) is the same as the maximum
+     * length of a GPT partition name.
+     */
+    char name[36];
+
+    /* 36: Attributes for the partition (see LP_PARTITION_ATTR_* flags above). */
+    uint32_t attributes;
+
+    /* 40: Index of the first extent owned by this partition. The extent will
+     * start at logical sector 0. Gaps between extents are not allowed.
+     */
+    uint32_t first_extent_index;
+
+    /* 44: Number of extents in the partition. Every partition must have at
+     * least one extent.
+     */
+    uint32_t num_extents;
+
+    /* 48: Group this partition belongs to. */
+    uint32_t group_index;
+} __attribute__((packed)) LpMetadataPartition;
+
+/* This extent is a dm-linear target, and the index is an index into the
+ * LinearExtent table.
+ */
+#define LP_TARGET_TYPE_LINEAR 0
+
+/* This extent is a dm-zero target. The index is ignored and must be 0. */
+#define LP_TARGET_TYPE_ZERO 1
+
+/* This struct defines an extent entry in the extent table block. */
+typedef struct LpMetadataExtent {
+    /*  0: Length of this extent, in 512-byte sectors. */
+    uint64_t num_sectors;
+
+    /*  8: Target type for device-mapper (see LP_TARGET_TYPE_* values). */
+    uint32_t target_type;
+
+    /* 12: Contents depends on target_type.
+     *
+     * LINEAR: The sector on the physical partition that this extent maps onto.
+     * ZERO: This field must be 0.
+     */
+    uint64_t target_data;
+
+    /* 20: Contents depends on target_type.
+     *
+     * LINEAR: Must be an index into the block devices table.
+     * ZERO: This field must be 0.
+     */
+    uint32_t target_source;
+} __attribute__((packed)) LpMetadataExtent;
+
+/* This struct defines an entry in the groups table. Each group has a maximum
+ * size, and partitions in a group must not exceed that size. There is always
+ * a "default" group of unlimited size, which is used when not using update
+ * groups or when using overlayfs or fastbootd.
+ */
+typedef struct LpMetadataPartitionGroup {
+    /*  0: Name of this group. Any unused characters must be 0. */
+    char name[36];
+
+    /* 36: Flags (see LP_GROUP_*). */
+    uint32_t flags;
+
+    /* 40: Maximum size in bytes. If 0, the group has no maximum size. */
+    uint64_t maximum_size;
+} __attribute__((packed)) LpMetadataPartitionGroup;
+
+/* This flag is only intended to be used with super_empty.img and super.img on
+ * retrofit devices. If set, the group needs a slot suffix to be interpreted
+ * correctly. The suffix is automatically applied by ReadMetadata().
+ */
+#define LP_GROUP_SLOT_SUFFIXED (1 << 0)
+
+/* This struct defines an entry in the block_devices table. There must be at
+ * least one device, and the first device must represent the partition holding
+ * the super metadata.
+ */
+typedef struct LpMetadataBlockDevice {
+    /* 0: First usable sector for allocating logical partitions. this will be
+     * the first sector after the initial geometry blocks, followed by the
+     * space consumed by metadata_max_size*metadata_slot_count*2.
+     */
+    uint64_t first_logical_sector;
+
+    /* 8: Alignment for defining partitions or partition extents. For example,
+     * an alignment of 1MiB will require that all partitions have a size evenly
+     * divisible by 1MiB, and that the smallest unit the partition can grow by
+     * is 1MiB.
+     *
+     * Alignment is normally determined at runtime when growing or adding
+     * partitions. If for some reason the alignment cannot be determined, then
+     * this predefined alignment in the geometry is used instead. By default
+     * it is set to 1MiB.
+     */
+    uint32_t alignment;
+
+    /* 12: Alignment offset for "stacked" devices. For example, if the "super"
+     * partition itself is not aligned within the parent block device's
+     * partition table, then we adjust for this in deciding where to place
+     * |first_logical_sector|.
+     *
+     * Similar to |alignment|, this will be derived from the operating system.
+     * If it cannot be determined, it is assumed to be 0.
+     */
+    uint32_t alignment_offset;
+
+    /* 16: Block device size, as specified when the metadata was created. This
+     * can be used to verify the geometry against a target device.
+     */
+    uint64_t size;
+
+    /* 24: Partition name in the GPT. Any unused characters must be 0. */
+    char partition_name[36];
+
+    /* 60: Flags (see LP_BLOCK_DEVICE_* flags below). */
+    uint32_t flags;
+} __attribute__((packed)) LpMetadataBlockDevice;
+
+/* This flag is only intended to be used with super_empty.img and super.img on
+ * retrofit devices. On these devices there are A and B super partitions, and
+ * we don't know ahead of time which slot the image will be applied to.
+ *
+ * If set, the block device needs a slot suffix applied before being used with
+ * IPartitionOpener. The slot suffix is determined by the metadata slot number
+ * (0 = _a, 1 = _b).
+ */
+#define LP_BLOCK_DEVICE_SLOT_SUFFIXED (1 << 0)
+
+/* For ease of writing compatibility checks, the original metadata header is
+ * preserved below, and typedefs are provided for the current version.
+ */
+typedef struct LpMetadataHeaderV1_0 {
+    uint32_t magic;
+    uint16_t major_version;
+    uint16_t minor_version;
+    uint32_t header_size;
+    uint8_t header_checksum[32];
+    uint32_t tables_size;
+    uint8_t tables_checksum[32];
+    LpMetadataTableDescriptor partitions;
+    LpMetadataTableDescriptor extents;
+    LpMetadataTableDescriptor groups;
+    LpMetadataTableDescriptor block_devices;
+} __attribute__((packed)) LpMetadataHeaderV1_0;
+
+typedef LpMetadataHeader LpMetadataHeaderV1_2;
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LOGICAL_PARTITION_METADATA_FORMAT_H_ */
diff --git a/src/parse-dynparts/liblp/reader.cpp b/src/parse-dynparts/liblp/reader.cpp
new file mode 100644 (file)
index 0000000..4f291be
--- /dev/null
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "reader.h"
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <functional>
+
+#include "utility.h"
+
+namespace android {
+namespace fs_mgr {
+
+static_assert(sizeof(LpMetadataHeaderV1_0) == offsetof(LpMetadataHeader, flags),
+              "Incorrect LpMetadataHeader v0 size");
+
+// Helper class for reading descriptors and memory buffers in the same manner.
+class Reader {
+  public:
+    virtual ~Reader(){};
+    virtual bool ReadFully(void* buffer, size_t length) = 0;
+};
+
+class FileReader final : public Reader {
+  public:
+    explicit FileReader(int fd) : fd_(fd) {}
+    bool ReadFully(void* buffer, size_t length) override {
+        return android::fs_mgr::ReadFully(fd_, buffer, length);
+    }
+
+  private:
+    int fd_;
+};
+
+class MemoryReader final : public Reader {
+  public:
+    MemoryReader(const void* buffer, size_t size)
+        : buffer_(reinterpret_cast<const uint8_t*>(buffer)), size_(size), pos_(0) {}
+    bool ReadFully(void* out, size_t length) override {
+        if (size_ - pos_ < length) {
+            errno = EINVAL;
+            return false;
+        }
+        memcpy(out, buffer_ + pos_, length);
+        pos_ += length;
+        return true;
+    }
+
+  private:
+    const uint8_t* buffer_;
+    size_t size_;
+    size_t pos_;
+};
+
+bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry) {
+    static_assert(sizeof(*geometry) <= LP_METADATA_GEOMETRY_SIZE);
+    memcpy(geometry, buffer, sizeof(*geometry));
+
+    // Check the magic signature.
+    if (geometry->magic != LP_METADATA_GEOMETRY_MAGIC) {
+        LERROR << "Logical partition metadata has invalid geometry magic signature.";
+        return false;
+    }
+    // Reject if the struct size is larger than what we compiled. This is so we
+    // can compute a checksum with the |struct_size| field rather than using
+    // sizeof.
+    if (geometry->struct_size > sizeof(LpMetadataGeometry)) {
+        LERROR << "Logical partition metadata has unrecognized fields.";
+        return false;
+    }
+    // Recompute and check the CRC32.
+    {
+        LpMetadataGeometry temp = *geometry;
+        memset(&temp.checksum, 0, sizeof(temp.checksum));
+        SHA256(&temp, temp.struct_size, temp.checksum);
+        if (memcmp(temp.checksum, geometry->checksum, sizeof(temp.checksum)) != 0) {
+            LERROR << "Logical partition metadata has invalid geometry checksum.";
+            return false;
+        }
+    }
+    // Check that the struct size is equal (this will have to change if we ever
+    // change the struct size in a release).
+    if (geometry->struct_size != sizeof(LpMetadataGeometry)) {
+        LERROR << "Logical partition metadata has invalid struct size.";
+        return false;
+    }
+    if (geometry->metadata_slot_count == 0) {
+        LERROR << "Logical partition metadata has invalid slot count.";
+        return false;
+    }
+    if (geometry->metadata_max_size % LP_SECTOR_SIZE != 0) {
+        LERROR << "Metadata max size is not sector-aligned.";
+        return false;
+    }
+    return true;
+}
+
+bool ReadPrimaryGeometry(int fd, LpMetadataGeometry* geometry) {
+    std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(LP_METADATA_GEOMETRY_SIZE);
+    if (SeekFile64(fd, GetPrimaryGeometryOffset(), SEEK_SET) < 0) {
+        PERROR << __PRETTY_FUNCTION__ << " lseek failed";
+        return false;
+    }
+    if (!ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) {
+        PERROR << __PRETTY_FUNCTION__ << " read " << LP_METADATA_GEOMETRY_SIZE << " bytes failed";
+        return false;
+    }
+    return ParseGeometry(buffer.get(), geometry);
+}
+
+bool ReadBackupGeometry(int fd, LpMetadataGeometry* geometry) {
+    std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(LP_METADATA_GEOMETRY_SIZE);
+    if (SeekFile64(fd, GetBackupGeometryOffset(), SEEK_SET) < 0) {
+        PERROR << __PRETTY_FUNCTION__ << " lseek failed";
+        return false;
+    }
+    if (!ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) {
+        PERROR << __PRETTY_FUNCTION__ << " backup read " << LP_METADATA_GEOMETRY_SIZE
+               << " bytes failed";
+        return false;
+    }
+    return ParseGeometry(buffer.get(), geometry);
+}
+
+// Read and validate geometry information from a block device that holds
+// logical partitions. If the information is corrupted, this will attempt
+// to read it from a secondary backup location.
+bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry) {
+    if (ReadPrimaryGeometry(fd, geometry)) {
+        return true;
+    }
+    return ReadBackupGeometry(fd, geometry);
+}
+
+static bool ValidateTableBounds(const LpMetadataHeader& header,
+                                const LpMetadataTableDescriptor& table) {
+    if (table.offset > header.tables_size) {
+        return false;
+    }
+    uint64_t table_size = uint64_t(table.num_entries) * table.entry_size;
+    if (header.tables_size - table.offset < table_size) {
+        return false;
+    }
+    return true;
+}
+
+static bool ReadMetadataHeader(Reader* reader, LpMetadata* metadata) {
+    // Note we zero the struct since older files will result in a partial read.
+    LpMetadataHeader& header = metadata->header;
+    memset(&header, 0, sizeof(header));
+
+    if (!reader->ReadFully(&header, sizeof(LpMetadataHeaderV1_0))) {
+        PERROR << __PRETTY_FUNCTION__ << " read failed";
+        return false;
+    }
+
+    // Do basic validity checks before computing the checksum.
+    if (header.magic != LP_METADATA_HEADER_MAGIC) {
+        LERROR << "Logical partition metadata has invalid magic value.";
+        return false;
+    }
+    if (header.major_version != LP_METADATA_MAJOR_VERSION ||
+        header.minor_version > LP_METADATA_MINOR_VERSION_MAX) {
+        LERROR << "Logical partition metadata has incompatible version.";
+        return false;
+    }
+
+    // Validate the header struct size against the reported version.
+    uint32_t expected_struct_size = sizeof(header);
+    if (header.minor_version < LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
+        expected_struct_size = sizeof(LpMetadataHeaderV1_0);
+    }
+    if (header.header_size != expected_struct_size) {
+        LERROR << "Invalid partition metadata header struct size.";
+        return false;
+    }
+
+    // Read in any remaining fields, the last step needed before checksumming.
+    if (size_t remaining_bytes = header.header_size - sizeof(LpMetadataHeaderV1_0)) {
+        uint8_t* offset = reinterpret_cast<uint8_t*>(&header) + sizeof(LpMetadataHeaderV1_0);
+        if (!reader->ReadFully(offset, remaining_bytes)) {
+            PERROR << __PRETTY_FUNCTION__ << " read failed";
+            return false;
+        }
+    }
+
+    // To compute the header's checksum, we have to temporarily set its checksum
+    // field to 0. Note that we must only compute up to |header_size|.
+    {
+        LpMetadataHeader temp = header;
+        memset(&temp.header_checksum, 0, sizeof(temp.header_checksum));
+        SHA256(&temp, temp.header_size, temp.header_checksum);
+        if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) !=
+            0) {
+            LERROR << "Logical partition metadata has invalid checksum.";
+            return false;
+        }
+    }
+
+    if (!ValidateTableBounds(header, header.partitions) ||
+        !ValidateTableBounds(header, header.extents) ||
+        !ValidateTableBounds(header, header.groups) ||
+        !ValidateTableBounds(header, header.block_devices)) {
+        LERROR << "Logical partition metadata has invalid table bounds.";
+        return false;
+    }
+    // Check that table entry sizes can accomodate their respective structs. If
+    // table sizes change, these checks will have to be adjusted.
+    if (header.partitions.entry_size != sizeof(LpMetadataPartition)) {
+        LERROR << "Logical partition metadata has invalid partition table entry size.";
+        return false;
+    }
+    if (header.extents.entry_size != sizeof(LpMetadataExtent)) {
+        LERROR << "Logical partition metadata has invalid extent table entry size.";
+        return false;
+    }
+    if (header.groups.entry_size != sizeof(LpMetadataPartitionGroup)) {
+        LERROR << "Logical partition metadata has invalid group table entry size.";
+        return false;
+    }
+    return true;
+}
+
+// Parse and validate all metadata at the current position in the given file
+// descriptor.
+static std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry,
+                                                 Reader* reader) {
+    // First read and validate the header.
+    std::unique_ptr<LpMetadata> metadata = std::make_unique<LpMetadata>();
+
+    metadata->geometry = geometry;
+    if (!ReadMetadataHeader(reader, metadata.get())) {
+        return nullptr;
+    }
+
+    LpMetadataHeader& header = metadata->header;
+
+    // Check the table size.
+    if (header.tables_size > geometry.metadata_max_size) {
+        LERROR << "Invalid partition metadata header table size.";
+        return nullptr;
+    }
+
+    // Read the metadata payload. Allocation is fallible since the table size
+    // could be large.
+    std::unique_ptr<uint8_t[]> buffer(new (std::nothrow) uint8_t[header.tables_size]);
+    if (!buffer) {
+        LERROR << "Out of memory reading logical partition tables.";
+        return nullptr;
+    }
+    if (!reader->ReadFully(buffer.get(), header.tables_size)) {
+        PERROR << __PRETTY_FUNCTION__ << " read " << header.tables_size << "bytes failed";
+        return nullptr;
+    }
+
+    uint8_t checksum[32];
+    SHA256(buffer.get(), header.tables_size, checksum);
+    if (memcmp(checksum, header.tables_checksum, sizeof(checksum)) != 0) {
+        LERROR << "Logical partition metadata has invalid table checksum.";
+        return nullptr;
+    }
+
+    uint32_t valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V0;
+    if (metadata->header.minor_version >= LP_METADATA_VERSION_FOR_UPDATED_ATTR) {
+        valid_attributes |= LP_PARTITION_ATTRIBUTE_MASK_V1;
+    }
+
+    // ValidateTableSize ensured that |cursor| is valid for the number of
+    // entries in the table.
+    uint8_t* cursor = buffer.get() + header.partitions.offset;
+    for (size_t i = 0; i < header.partitions.num_entries; i++) {
+        LpMetadataPartition partition;
+        memcpy(&partition, cursor, sizeof(partition));
+        cursor += header.partitions.entry_size;
+
+        if (partition.attributes & ~valid_attributes) {
+            LERROR << "Logical partition has invalid attribute set.";
+            return nullptr;
+        }
+        if (partition.first_extent_index + partition.num_extents < partition.first_extent_index) {
+            LERROR << "Logical partition first_extent_index + num_extents overflowed.";
+            return nullptr;
+        }
+        if (partition.first_extent_index + partition.num_extents > header.extents.num_entries) {
+            LERROR << "Logical partition has invalid extent list.";
+            return nullptr;
+        }
+        if (partition.group_index >= header.groups.num_entries) {
+            LERROR << "Logical partition has invalid group index.";
+            return nullptr;
+        }
+
+        metadata->partitions.push_back(partition);
+    }
+
+    cursor = buffer.get() + header.extents.offset;
+    for (size_t i = 0; i < header.extents.num_entries; i++) {
+        LpMetadataExtent extent;
+        memcpy(&extent, cursor, sizeof(extent));
+        cursor += header.extents.entry_size;
+
+        if (extent.target_type == LP_TARGET_TYPE_LINEAR &&
+            extent.target_source >= header.block_devices.num_entries) {
+            LERROR << "Logical partition extent has invalid block device.";
+            return nullptr;
+        }
+
+        metadata->extents.push_back(extent);
+    }
+
+    cursor = buffer.get() + header.groups.offset;
+    for (size_t i = 0; i < header.groups.num_entries; i++) {
+        LpMetadataPartitionGroup group = {};
+        memcpy(&group, cursor, sizeof(group));
+        cursor += header.groups.entry_size;
+
+        metadata->groups.push_back(group);
+    }
+
+    cursor = buffer.get() + header.block_devices.offset;
+    for (size_t i = 0; i < header.block_devices.num_entries; i++) {
+        LpMetadataBlockDevice device = {};
+        memcpy(&device, cursor, sizeof(device));
+        cursor += header.block_devices.entry_size;
+
+        metadata->block_devices.push_back(device);
+    }
+
+    const LpMetadataBlockDevice* super_device = GetMetadataSuperBlockDevice(*metadata.get());
+    if (!super_device) {
+        LERROR << "Metadata does not specify a super device.";
+        return nullptr;
+    }
+
+    // Check that the metadata area and logical partition areas don't overlap.
+    uint64_t metadata_region =
+            GetTotalMetadataSize(geometry.metadata_max_size, geometry.metadata_slot_count);
+    if (metadata_region > super_device->first_logical_sector * LP_SECTOR_SIZE) {
+        LERROR << "Logical partition metadata overlaps with logical partition contents.";
+        return nullptr;
+    }
+    return metadata;
+}
+
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer,
+                                          size_t size) {
+    MemoryReader reader(buffer, size);
+    return ParseMetadata(geometry, &reader);
+}
+
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, int fd) {
+    FileReader reader(fd);
+    return ParseMetadata(geometry, &reader);
+}
+
+std::unique_ptr<LpMetadata> ReadPrimaryMetadata(int fd, const LpMetadataGeometry& geometry,
+                                                uint32_t slot_number) {
+    int64_t offset = GetPrimaryMetadataOffset(geometry, slot_number);
+    if (SeekFile64(fd, offset, SEEK_SET) < 0) {
+        PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << offset;
+        return nullptr;
+    }
+    return ParseMetadata(geometry, fd);
+}
+
+std::unique_ptr<LpMetadata> ReadBackupMetadata(int fd, const LpMetadataGeometry& geometry,
+                                               uint32_t slot_number) {
+    int64_t offset = GetBackupMetadataOffset(geometry, slot_number);
+    if (SeekFile64(fd, offset, SEEK_SET) < 0) {
+        PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << offset;
+        return nullptr;
+    }
+    return ParseMetadata(geometry, fd);
+}
+
+namespace {
+
+bool AdjustMetadataForSlot(LpMetadata* metadata, uint32_t slot_number) {
+    std::string slot_suffix = SlotSuffixForSlotNumber(slot_number);
+    for (auto& partition : metadata->partitions) {
+        if (!(partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED)) {
+            continue;
+        }
+        std::string partition_name = GetPartitionName(partition) + slot_suffix;
+        if (partition_name.size() > sizeof(partition.name)) {
+            LERROR << __PRETTY_FUNCTION__ << " partition name too long: " << partition_name;
+            return false;
+        }
+        strncpy(partition.name, partition_name.c_str(), sizeof(partition.name));
+        partition.attributes &= ~LP_PARTITION_ATTR_SLOT_SUFFIXED;
+    }
+    for (auto& block_device : metadata->block_devices) {
+        if (!(block_device.flags & LP_BLOCK_DEVICE_SLOT_SUFFIXED)) {
+            continue;
+        }
+        std::string partition_name = GetBlockDevicePartitionName(block_device) + slot_suffix;
+        if (!UpdateBlockDevicePartitionName(&block_device, partition_name)) {
+            LERROR << __PRETTY_FUNCTION__ << " partition name too long: " << partition_name;
+            return false;
+        }
+        block_device.flags &= ~LP_BLOCK_DEVICE_SLOT_SUFFIXED;
+    }
+    for (auto& group : metadata->groups) {
+        if (!(group.flags & LP_GROUP_SLOT_SUFFIXED)) {
+            continue;
+        }
+        std::string group_name = GetPartitionGroupName(group) + slot_suffix;
+        if (!UpdatePartitionGroupName(&group, group_name)) {
+            LERROR << __PRETTY_FUNCTION__ << " group name too long: " << group_name;
+            return false;
+        }
+        group.flags &= ~LP_GROUP_SLOT_SUFFIXED;
+    }
+    return true;
+}
+
+}  // namespace
+
+std::unique_ptr<LpMetadata> ReadMetadata(const std::string& super_partition, uint32_t slot_number) {
+    int fd = open(super_partition.c_str(), O_RDONLY);
+    if (fd < 0) {
+        PERROR << __PRETTY_FUNCTION__ << " open failed: " << super_partition;
+        return nullptr;
+    }
+
+    LpMetadataGeometry geometry;
+    if (!ReadLogicalPartitionGeometry(fd, &geometry)) {
+        close(fd);
+        return nullptr;
+    }
+    if (slot_number >= geometry.metadata_slot_count) {
+        LERROR << __PRETTY_FUNCTION__ << " invalid metadata slot number";
+        close(fd);
+        return nullptr;
+    }
+
+    std::vector<int64_t> offsets = {
+            GetPrimaryMetadataOffset(geometry, slot_number),
+            GetBackupMetadataOffset(geometry, slot_number),
+    };
+    std::unique_ptr<LpMetadata> metadata;
+
+    for (const auto& offset : offsets) {
+        if (SeekFile64(fd, offset, SEEK_SET) < 0) {
+            PERROR << __PRETTY_FUNCTION__ << " lseek failed, offset " << offset;
+            continue;
+        }
+        if ((metadata = ParseMetadata(geometry, fd)) != nullptr) {
+            break;
+        }
+    }
+    if (!metadata || !AdjustMetadataForSlot(metadata.get(), slot_number)) {
+        close(fd);
+        return nullptr;
+    }
+
+    close(fd);
+    return metadata;
+}
+
+static std::string NameFromFixedArray(const char* name, size_t buffer_size) {
+    // If the end of the buffer has a null character, it's safe to assume the
+    // buffer is null terminated. Otherwise, we cap the string to the input
+    // buffer size.
+    if (name[buffer_size - 1] == '\0') {
+        return std::string(name);
+    }
+    return std::string(name, buffer_size);
+}
+
+std::string GetPartitionName(const LpMetadataPartition& partition) {
+    return NameFromFixedArray(partition.name, sizeof(partition.name));
+}
+
+std::string GetPartitionGroupName(const LpMetadataPartitionGroup& group) {
+    return NameFromFixedArray(group.name, sizeof(group.name));
+}
+
+std::string GetBlockDevicePartitionName(const LpMetadataBlockDevice& block_device) {
+    return NameFromFixedArray(block_device.partition_name, sizeof(block_device.partition_name));
+}
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/src/parse-dynparts/liblp/reader.h b/src/parse-dynparts/liblp/reader.h
new file mode 100644 (file)
index 0000000..7a2490b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBLP_READER_H_
+#define LIBLP_READER_H_
+
+#include <stddef.h>
+
+#include <memory>
+
+#include <liblp/liblp.h>
+
+namespace android {
+namespace fs_mgr {
+
+// Parse an LpMetadataGeometry from a buffer. The buffer must be at least
+// LP_METADATA_GEOMETRY_SIZE bytes in size.
+bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry);
+
+// Helper functions for manually reading geometry and metadata.
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, int fd);
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer,
+                                          size_t size);
+bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry);
+bool ReadPrimaryGeometry(int fd, LpMetadataGeometry* geometry);
+bool ReadBackupGeometry(int fd, LpMetadataGeometry* geometry);
+
+// These functions assume a valid geometry and slot number, and do not obey
+// auto-slot-suffixing. They are used for tests and for checking whether
+// the metadata is coherent across primary and backup copies.
+std::unique_ptr<LpMetadata> ReadPrimaryMetadata(int fd, const LpMetadataGeometry& geometry,
+                                                uint32_t slot_number);
+std::unique_ptr<LpMetadata> ReadBackupMetadata(int fd, const LpMetadataGeometry& geometry,
+                                               uint32_t slot_number);
+
+}  // namespace fs_mgr
+}  // namespace android
+
+#endif /* LIBLP_READER_H_ */
diff --git a/src/parse-dynparts/liblp/utility.cpp b/src/parse-dynparts/liblp/utility.cpp
new file mode 100644 (file)
index 0000000..afab237
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#if defined(__linux__)
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#endif
+
+#include <cstring>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fs_mgr {
+
+int64_t SeekFile64(int fd, int64_t offset, int whence) {
+    static_assert(sizeof(off_t) == sizeof(int64_t), "Need 64-bit lseek");
+    return lseek(fd, offset, whence);
+}
+
+int64_t GetPrimaryGeometryOffset() {
+    return LP_PARTITION_RESERVED_BYTES;
+}
+
+int64_t GetBackupGeometryOffset() {
+    return GetPrimaryGeometryOffset() + LP_METADATA_GEOMETRY_SIZE;
+}
+
+int64_t GetPrimaryMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number) {
+    CHECK(slot_number < geometry.metadata_slot_count);
+    int64_t offset = LP_PARTITION_RESERVED_BYTES + (LP_METADATA_GEOMETRY_SIZE * 2) +
+                     geometry.metadata_max_size * slot_number;
+    return offset;
+}
+
+int64_t GetBackupMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number) {
+    CHECK(slot_number < geometry.metadata_slot_count);
+    int64_t start = LP_PARTITION_RESERVED_BYTES + (LP_METADATA_GEOMETRY_SIZE * 2) +
+                    int64_t(geometry.metadata_max_size) * geometry.metadata_slot_count;
+    return start + int64_t(geometry.metadata_max_size * slot_number);
+}
+
+uint64_t GetTotalMetadataSize(uint32_t metadata_max_size, uint32_t max_slots) {
+    return LP_PARTITION_RESERVED_BYTES +
+           (LP_METADATA_GEOMETRY_SIZE + metadata_max_size * max_slots) * 2;
+}
+
+const LpMetadataBlockDevice* GetMetadataSuperBlockDevice(const LpMetadata& metadata) {
+    if (metadata.block_devices.empty()) {
+        return nullptr;
+    }
+    return &metadata.block_devices[0];
+}
+
+void SHA256(const void* data, size_t length, uint8_t out[32]) {
+    const EVP_MD *md = EVP_sha256();
+
+    if (!EVP_Digest(data, length, out, nullptr, md, nullptr))
+        LERROR << __PRETTY_FUNCTION__ << "Unable to compute hash";
+}
+
+uint32_t SlotNumberForSlotSuffix(const std::string& suffix) {
+    if (suffix.empty() || suffix == "a" || suffix == "_a") {
+        return 0;
+    } else if (suffix == "b" || suffix == "_b") {
+        return 1;
+    } else {
+        LERROR << __PRETTY_FUNCTION__ << "slot '" << suffix
+               << "' does not have a recognized format.";
+        return 0;
+    }
+}
+
+uint64_t GetTotalSuperPartitionSize(const LpMetadata& metadata) {
+    uint64_t size = 0;
+    for (const auto& block_device : metadata.block_devices) {
+        size += block_device.size;
+    }
+    return size;
+}
+
+std::vector<std::string> GetBlockDevicePartitionNames(const LpMetadata& metadata) {
+    std::vector<std::string> list;
+    for (const auto& block_device : metadata.block_devices) {
+        list.emplace_back(GetBlockDevicePartitionName(block_device));
+    }
+    return list;
+}
+
+const LpMetadataPartition* FindPartition(const LpMetadata& metadata, const std::string& name) {
+    for (const auto& partition : metadata.partitions) {
+        if (GetPartitionName(partition) == name) {
+            return &partition;
+        }
+    }
+    return nullptr;
+}
+
+uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) {
+    uint64_t total_size = 0;
+    for (uint32_t i = 0; i < partition.num_extents; i++) {
+        const auto& extent = metadata.extents[partition.first_extent_index + i];
+        total_size += extent.num_sectors * LP_SECTOR_SIZE;
+    }
+    return total_size;
+}
+
+std::string GetPartitionSlotSuffix(const std::string& partition_name) {
+    if (partition_name.size() <= 2) {
+        return "";
+    }
+    std::string suffix = partition_name.substr(partition_name.size() - 2);
+    return (suffix == "_a" || suffix == "_b") ? suffix : "";
+}
+
+std::string SlotSuffixForSlotNumber(uint32_t slot_number) {
+    CHECK(slot_number == 0 || slot_number == 1);
+    return (slot_number == 0) ? "_a" : "_b";
+}
+
+bool UpdateBlockDevicePartitionName(LpMetadataBlockDevice* device, const std::string& name) {
+    if (name.size() > sizeof(device->partition_name)) {
+        return false;
+    }
+    strncpy(device->partition_name, name.c_str(), sizeof(device->partition_name));
+    return true;
+}
+
+bool UpdatePartitionGroupName(LpMetadataPartitionGroup* group, const std::string& name) {
+    if (name.size() > sizeof(group->name)) {
+        return false;
+    }
+    strncpy(group->name, name.c_str(), sizeof(group->name));
+    return true;
+}
+
+bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name) {
+    if (name.size() > sizeof(partition->name)) {
+        return false;
+    }
+    strncpy(partition->name, name.c_str(), sizeof(partition->name));
+    return true;
+}
+
+bool SetBlockReadonly(int fd, bool readonly) {
+#if defined(__linux__)
+    int val = readonly;
+    return ioctl(fd, BLKROSET, &val) == 0;
+#else
+    (void)fd;
+    (void)readonly;
+    return true;
+#endif
+}
+
+inline std::string ToHexString(uint64_t value) {
+    std::ostringstream stream;
+    stream << "0x" << std::hex << value;
+    return stream.str();
+}
+
+void SetMetadataHeaderV0(LpMetadata* metadata) {
+    if (metadata->header.minor_version <= LP_METADATA_MINOR_VERSION_MIN) {
+        return;
+    }
+    LINFO << "Forcefully setting metadata header version " << LP_METADATA_MAJOR_VERSION << "."
+          << metadata->header.minor_version << " to " << LP_METADATA_MAJOR_VERSION << "."
+          << LP_METADATA_MINOR_VERSION_MIN;
+    metadata->header.minor_version = LP_METADATA_MINOR_VERSION_MIN;
+    metadata->header.header_size = sizeof(LpMetadataHeaderV1_0);
+
+    // Retrofit Virtual A/B devices should have version 10.1, so flags shouldn't be set.
+    // Warn if this is the case, but zero it out anyways.
+    if (metadata->header.flags) {
+        LWARN << "Zeroing unexpected flags: " << ToHexString(metadata->header.flags);
+    }
+
+    // Zero out all fields beyond LpMetadataHeaderV0.
+    static_assert(sizeof(metadata->header) > sizeof(LpMetadataHeaderV1_0));
+    memset(reinterpret_cast<uint8_t*>(&metadata->header) + sizeof(LpMetadataHeaderV1_0), 0,
+           sizeof(metadata->header) - sizeof(LpMetadataHeaderV1_0));
+
+    // Clear partition attributes unknown to V0.
+    // On retrofit Virtual A/B devices, UPDATED flag may be set, so only log info here.
+    for (auto& partition : metadata->partitions) {
+        if (partition.attributes & ~LP_PARTITION_ATTRIBUTE_MASK_V0) {
+            LINFO << "Clearing " << GetPartitionName(partition)
+                  << " partition attribute: " << ToHexString(partition.attributes);
+        }
+
+        partition.attributes &= LP_PARTITION_ATTRIBUTE_MASK_V0;
+    }
+}
+
+bool ReadFully(int fd, void* data, size_t byte_count) {
+  uint8_t* p = reinterpret_cast<uint8_t*>(data);
+  size_t remaining = byte_count;
+  while (remaining > 0) {
+    ssize_t n = TEMP_FAILURE_RETRY(read(fd, p, remaining));
+    if (n <= 0) return false;
+    p += n;
+    remaining -= n;
+  }
+  return true;
+}
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/src/parse-dynparts/liblp/utility.h b/src/parse-dynparts/liblp/utility.h
new file mode 100644 (file)
index 0000000..6b17abe
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBLP_UTILITY_H
+#define LIBLP_UTILITY_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <cassert>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+#include <liblp/liblp.h>
+
+#define LP_TAG "[liblp]"
+#define LWARN NewlineLogger(std::cerr).stream << "[W]" << LP_TAG
+#define LINFO NewlineLogger(std::cerr).stream << "[I]" << LP_TAG
+#define LERROR NewlineLogger(std::cerr).stream << "[E]" << LP_TAG
+#define PWARNING LWARN
+#define PERROR LERROR
+
+#define CHECK assert
+
+namespace android {
+namespace fs_mgr {
+
+// Return the offset of the primary or backup geometry.
+int64_t GetPrimaryGeometryOffset();
+int64_t GetBackupGeometryOffset();
+
+// Return the offset of a primary metadata slot, relative to the start of the
+// device.
+int64_t GetPrimaryMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number);
+
+// Return the offset of a backup metadata slot, relative to the end of the
+// device.
+int64_t GetBackupMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number);
+
+// Return the total space at the start of the super partition that must be set
+// aside from headers/metadata and backups.
+uint64_t GetTotalMetadataSize(uint32_t metadata_max_size, uint32_t max_slots);
+
+// Cross-platform helper for lseek64().
+int64_t SeekFile64(int fd, int64_t offset, int whence);
+
+// Compute a SHA256 hash.
+void SHA256(const void* data, size_t length, uint8_t out[32]);
+
+// Align |base| such that it is evenly divisible by |alignment|, which does not
+// have to be a power of two. Return false on overflow.
+template <typename T>
+bool AlignTo(T base, uint32_t alignment, T* out) {
+    static_assert(std::numeric_limits<T>::is_integer);
+    static_assert(!std::numeric_limits<T>::is_signed);
+    if (!alignment) {
+        *out = base;
+        return true;
+    }
+    T remainder = base % alignment;
+    if (remainder == 0) {
+        *out = base;
+        return true;
+    }
+    T to_add = alignment - remainder;
+    if (to_add > std::numeric_limits<T>::max() - base) {
+        return false;
+    }
+    *out = base + to_add;
+    return true;
+}
+
+// Update names from C++ strings.
+bool UpdateBlockDevicePartitionName(LpMetadataBlockDevice* device, const std::string& name);
+bool UpdatePartitionGroupName(LpMetadataPartitionGroup* group, const std::string& name);
+bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name);
+
+// Call BLKROSET ioctl on fd so that fd is readonly / read-writable.
+bool SetBlockReadonly(int fd, bool readonly);
+
+// Forcefully set metadata header version to 1.0, clearing any incompatible flags and attributes
+// so that when downgrading to a build with liblp V0, the device still boots.
+void SetMetadataHeaderV0(LpMetadata* metadata);
+
+bool ReadFully(int fd, void* data, size_t byte_count);
+
+class NewlineLogger {
+  public:
+    NewlineLogger(std::ostream& sink) : sink_(sink) {}
+    ~NewlineLogger() {
+        sink_ << stream.str() << std::endl;
+    }
+
+    std::ostringstream stream;
+
+  private:
+    std::ostream& sink_;
+};
+
+}  // namespace fs_mgr
+}  // namespace android
+
+#endif  // LIBLP_UTILITY_H
diff --git a/src/parse-dynparts/main.cpp b/src/parse-dynparts/main.cpp
new file mode 100644 (file)
index 0000000..35fe9b5
--- /dev/null
@@ -0,0 +1,43 @@
+/* Copyright (c) 2021 Tom Hebb (https://github.com/tchebb/parse-android-dynparts)
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: Apache-2.0 */
+
+#include <liblp/liblp.h>
+
+#include <iostream>
+#include <sstream>
+#include <string_view>
+
+#include "lib.hpp"
+
+using namespace android::fs_mgr;
+
+using std::cerr;
+using std::cout;
+using std::endl;
+
+int main(int argc, char* argv[]) {
+  if (argc < 2 || argc > 3 || (argc == 3 && std::string_view(argv[2]).compare("--list-tables"))) {
+    cerr << "Usage: dmsetup create --concise \"$(" << argv[0] << " device)\"" << endl
+         << "Alternatively, you can use --list-tables option to get partitions tables line"
+         << " by line which you can provide to dmsetup create."
+         << endl;
+    return 1;
+  }
+
+  bool list_tables = (argc == 3) ? true : false;
+
+  auto metadata = ReadMetadata(argv[1], 0);
+  if (!metadata) {
+    cerr << "Failed to parse metadata from \"" << argv[1] << "\"" << endl;
+    return 1;
+  }
+
+  auto out = go(*metadata, list_tables, argv[1], cerr);
+  if (out)
+    cout << *out << "\n";
+  else
+    return 1;
+
+  return 0;
+}
diff --git a/src/parse-dynparts/test/generate_test_data.sh b/src/parse-dynparts/test/generate_test_data.sh
new file mode 100755 (executable)
index 0000000..0c99cb9
--- /dev/null
@@ -0,0 +1,339 @@
+ #!/bin/bash
+
+MIB=1048576 # 1 MiB is this amount of bytes
+SUPER_ALIGNMENT=$MIB
+
+# calculate aligned sized of partitions to be placed on super
+get_aligned_size () {
+    func_result=$(($SUPER_ALIGNMENT*(($1+$SUPER_ALIGNMENT-1)/$SUPER_ALIGNMENT)))
+    echo "$func_result"
+}
+
+# super with one slot, one partition
+create_super_1 () {
+    metadata_slots=1
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=6 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$(($super_size-$metadata_aligned_size))"
+    part_size="$group_size" # 5MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_1.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a"
+}
+
+# super with one slot, two partitions
+create_super_2 () {
+    metadata_slots=1
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=11 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$(($super_size-$metadata_aligned_size))"
+    part_size="$(($group_size/2))" # 5MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_2.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a" \
+           -p "part_2_a:none:$part_size:group_a"
+}
+
+# super with one slot, three partitions
+# super has 31 MB becuase without metadata there is 60 MB for partitions which nicely divides by 3 and by 6
+create_super_3 () {
+    metadata_slots=1
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=16 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$(($super_size-$metadata_aligned_size))"
+    part_size="$(($group_size/3))" # 5MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_3.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a" \
+           -p "part_2_a:none:$part_size:group_a" \
+           -p "part_3_a:none:$part_size:group_a"
+}
+
+# super with one slot, four partitions
+create_super_4 () {
+    metadata_slots=1
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=21 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$(($super_size-$metadata_aligned_size))"
+    part_size="$(($group_size/4))" # 5MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_4.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a" \
+           -p "part_2_a:none:$part_size:group_a" \
+           -p "part_3_a:none:$part_size:group_a" \
+           -p "part_4_a:none:$part_size:group_a"
+}
+
+# super with two slots, one partition each
+create_super_5 () {
+    metadata_slots=2
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=11 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$((($super_size-$metadata_aligned_size)/2))"
+    part_size="$group_size" # 5MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_5.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a" \
+           -g "group_b:$group_size" \
+           -p "part_1_b:none:$part_size:group_b"
+}
+
+# super with two slots, two partitions each
+create_super_6 () {
+    metadata_slots=2
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=21 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$((($super_size-$metadata_aligned_size)/2))"
+    part_size="$(($group_size/2))" # 5MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_6.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a" \
+           -p "part_2_a:none:$part_size:group_a" \
+           -g "group_b:$group_size" \
+           -p "part_1_b:none:$part_size:group_b" \
+           -p "part_2_b:none:$part_size:group_b"
+}
+
+# super with two slots, three partitions each
+create_super_7 () {
+    metadata_slots=2
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=31 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$((($super_size-$metadata_aligned_size)/2))"
+    part_size="$(($group_size/3))" # 5MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_7.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a" \
+           -p "part_2_a:none:$part_size:group_a" \
+           -p "part_3_a:none:$part_size:group_a" \
+           -g "group_b:$group_size" \
+           -p "part_1_b:none:$part_size:group_b" \
+           -p "part_2_b:none:$part_size:group_b" \
+           -p "part_3_b:none:$part_size:group_b"
+}
+
+# super with two slots, four partitions each
+create_super_8 () {
+    metadata_slots=2
+    metadata_size=65536
+    metadata_aligned_size="$(get_aligned_size $metadata_size)" #should be 1 MB
+
+    super_size_mb=41 # MB
+    super_size="$(($super_size_mb*$MIB))"
+    group_size="$((($super_size-$metadata_aligned_size)/2))"
+    part_size="$(($group_size/4))" # 10 MB
+
+    lpmake -F --device-size=$super_size \
+           --metadata-size=$metadata_size \
+           --metadata-slots=$metadata_slots \
+           -o=super_8.img \
+           -g "group_a:$group_size" \
+           -p "part_1_a:none:$part_size:group_a" \
+           -p "part_2_a:none:$part_size:group_a" \
+           -p "part_3_a:none:$part_size:group_a" \
+           -p "part_4_a:none:$part_size:group_a" \
+           -g "group_b:$group_size" \
+           -p "part_1_b:none:$part_size:group_b" \
+           -p "part_2_b:none:$part_size:group_b" \
+           -p "part_3_b:none:$part_size:group_b" \
+           -p "part_4_b:none:$part_size:group_b"
+}
+
+create_super_1
+create_super_2
+create_super_3
+create_super_4
+create_super_5
+create_super_6
+create_super_7
+create_super_8
+
+one_mib_part_size_blocks="$(($MIB/512))"
+two_mib_part_size_blocks="$((2*$MIB/512))"
+three_mib_part_size_blocks="$((3*$MIB/512))"
+four_mib_part_size_blocks="$((4*$MIB/512))"
+five_mib_part_size_blocks="$((5*$MIB/512))"
+ten_mib_part_size_blocks="$((10*$MIB/512))"
+
+dd if=/dev/null of=part_one_mib.img seek="$one_mib_part_size_blocks"
+dd if=/dev/null of=part_two_mib.img seek="$two_mib_part_size_blocks"
+dd if=/dev/null of=part_three_mib.img seek="$three_mib_part_size_blocks"
+dd if=/dev/null of=part_four_mib.img seek="$four_mib_part_size_blocks"
+dd if=/dev/null of=part_five_mib.img seek="$five_mib_part_size_blocks"
+dd if=/dev/null of=part_ten_mib.img seek="$ten_mib_part_size_blocks"
+
+cp super_3.img super_partitioned_1.img
+
+lpadd super_partitioned_1.img part_1_a group_a part_one_mib.img --replace
+lpadd super_partitioned_1.img part_3_a group_a part_one_mib.img --replace
+lpadd super_partitioned_1.img part_4_a group_a part_five_mib.img
+
+cp super_3.img super_partitioned_2.img
+
+lpadd super_partitioned_2.img part_1_a group_a part_one_mib.img --replace
+lpadd super_partitioned_2.img part_3_a group_a part_one_mib.img --replace
+lpadd super_partitioned_2.img part_4_a group_a part_five_mib.img
+lpadd super_partitioned_2.img part_2_a group_a part_one_mib.img --replace
+lpadd super_partitioned_2.img part_5_a group_a part_five_mib.img
+
+cp super_4.img super_partitioned_3.img
+
+lpadd super_partitioned_3.img part_3_a group_a part_one_mib.img --replace
+lpadd super_partitioned_3.img part_2_a group_a part_one_mib.img --replace
+lpadd super_partitioned_3.img part_1_a group_a part_one_mib.img --replace
+lpadd super_partitioned_3.img part_5_a group_a part_ten_mib.img
+
+cp super_7.img super_partitioned_4.img
+
+lpadd super_partitioned_4.img part_1_a group_a part_one_mib.img --replace
+lpadd super_partitioned_4.img part_3_a group_a part_one_mib.img --replace
+lpadd super_partitioned_4.img part_4_a group_a part_five_mib.img
+
+cp super_7.img super_partitioned_5.img
+
+lpadd super_partitioned_5.img part_1_b group_b part_one_mib.img --replace
+lpadd super_partitioned_5.img part_3_b group_b part_one_mib.img --replace
+lpadd super_partitioned_5.img part_4_b group_b part_five_mib.img
+
+cp super_7.img super_partitioned_6.img
+
+lpadd super_partitioned_6.img part_1_a group_a part_one_mib.img --replace
+lpadd super_partitioned_6.img part_3_a group_a part_one_mib.img --replace
+lpadd super_partitioned_6.img part_4_a group_a part_five_mib.img
+
+lpadd super_partitioned_6.img part_1_b group_b part_one_mib.img --replace
+lpadd super_partitioned_6.img part_3_b group_b part_one_mib.img --replace
+lpadd super_partitioned_6.img part_4_b group_b part_five_mib.img
+
+cp super_8.img super_partitioned_7.img
+
+lpadd super_partitioned_7.img part_3_a group_a part_one_mib.img --replace
+lpadd super_partitioned_7.img part_2_a group_a part_one_mib.img --replace
+lpadd super_partitioned_7.img part_1_a group_a part_one_mib.img --replace
+lpadd super_partitioned_7.img part_5_a group_a part_ten_mib.img
+
+cp super_8.img super_partitioned_8.img
+
+lpadd super_partitioned_8.img part_3_b group_b part_one_mib.img --replace
+lpadd super_partitioned_8.img part_2_b group_b part_one_mib.img --replace
+lpadd super_partitioned_8.img part_1_b group_b part_one_mib.img --replace
+lpadd super_partitioned_8.img part_5_b group_b part_ten_mib.img
+
+
+cp super_8.img super_partitioned_9.img
+
+lpadd super_partitioned_9.img part_3_a group_a part_one_mib.img --replace
+lpadd super_partitioned_9.img part_2_a group_a part_one_mib.img --replace
+lpadd super_partitioned_9.img part_1_a group_a part_one_mib.img --replace
+lpadd super_partitioned_9.img part_5_a group_a part_ten_mib.img
+
+lpadd super_partitioned_9.img part_3_b group_b part_one_mib.img --replace
+lpadd super_partitioned_9.img part_2_b group_b part_one_mib.img --replace
+lpadd super_partitioned_9.img part_1_b group_b part_one_mib.img --replace
+lpadd super_partitioned_9.img part_5_b group_b part_ten_mib.img
+
+# super_dump is a simple tool which reads metadata
+# from an image and prints it to stdin using metadataio.h
+super_dump super_1.img > super_1.in
+super_dump super_2.img > super_2.in
+super_dump super_3.img > super_3.in
+super_dump super_4.img > super_4.in
+super_dump super_5.img > super_5.in
+super_dump super_6.img > super_6.in
+super_dump super_7.img > super_7.in
+super_dump super_8.img > super_8.in
+super_dump super_partitioned_1.img > super_partitioned_1.in
+super_dump super_partitioned_2.img > super_partitioned_2.in
+super_dump super_partitioned_3.img > super_partitioned_3.in
+super_dump super_partitioned_4.img > super_partitioned_4.in
+super_dump super_partitioned_5.img > super_partitioned_5.in
+super_dump super_partitioned_6.img > super_partitioned_6.in
+super_dump super_partitioned_7.img > super_partitioned_7.in
+super_dump super_partitioned_8.img > super_partitioned_8.in
+super_dump super_partitioned_9.img > super_partitioned_9.in
+
+parse-dynparts super_1.img > super_1.out
+parse-dynparts super_2.img > super_2.out
+parse-dynparts super_3.img > super_3.out
+parse-dynparts super_4.img > super_4.out
+parse-dynparts super_5.img > super_5.out
+parse-dynparts super_6.img > super_6.out
+parse-dynparts super_7.img > super_7.out
+parse-dynparts super_8.img > super_8.out
+parse-dynparts super_partitioned_1.img > super_partitioned_1.out
+parse-dynparts super_partitioned_2.img > super_partitioned_2.out
+parse-dynparts super_partitioned_3.img > super_partitioned_3.out
+parse-dynparts super_partitioned_4.img > super_partitioned_4.out
+parse-dynparts super_partitioned_5.img > super_partitioned_5.out
+parse-dynparts super_partitioned_6.img > super_partitioned_6.out
+parse-dynparts super_partitioned_7.img > super_partitioned_7.out
+parse-dynparts super_partitioned_8.img > super_partitioned_8.out
+parse-dynparts super_partitioned_9.img > super_partitioned_9.out
+
+parse-dynparts super_1.img --list-tables > super_1_list.out
+parse-dynparts super_2.img --list-tables > super_2_list.out
+parse-dynparts super_3.img --list-tables > super_3_list.out
+parse-dynparts super_4.img --list-tables > super_4_list.out
+parse-dynparts super_5.img --list-tables > super_5_list.out
+parse-dynparts super_6.img --list-tables > super_6_list.out
+parse-dynparts super_7.img --list-tables > super_7_list.out
+parse-dynparts super_8.img --list-tables > super_8_list.out
+parse-dynparts super_partitioned_1.img --list-tables > super_partitioned_1_list.out
+parse-dynparts super_partitioned_2.img --list-tables > super_partitioned_2_list.out
+parse-dynparts super_partitioned_3.img --list-tables > super_partitioned_3_list.out
+parse-dynparts super_partitioned_4.img --list-tables > super_partitioned_4_list.out
+parse-dynparts super_partitioned_5.img --list-tables > super_partitioned_5_list.out
+parse-dynparts super_partitioned_6.img --list-tables > super_partitioned_6_list.out
+parse-dynparts super_partitioned_7.img --list-tables > super_partitioned_7_list.out
+parse-dynparts super_partitioned_8.img --list-tables > super_partitioned_8_list.out
+parse-dynparts super_partitioned_9.img --list-tables > super_partitioned_9_list.out
+
diff --git a/src/parse-dynparts/test/metadataio.cpp b/src/parse-dynparts/test/metadataio.cpp
new file mode 100644 (file)
index 0000000..c6f500c
--- /dev/null
@@ -0,0 +1,398 @@
+/* Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: MIT */
+
+#include "metadataio.h"
+
+bool metadataio::operator==(const LpMetadataBlockDevice &a, const LpMetadataBlockDevice &b) {
+    if (a.first_logical_sector != b.first_logical_sector) return false;
+    if (a.alignment != b.alignment) return false;
+    if (a.alignment_offset != b.alignment_offset) return false;
+    if (a.size != b.size) return false;
+    if (a.flags != b.flags) return false;
+
+    for(int i = 0; i < 36; ++i)
+        if (a.partition_name[i] != b.partition_name[i]) return false;
+
+    return true;
+}
+
+bool metadataio::operator==(const LpMetadataPartitionGroup &a, const LpMetadataPartitionGroup &b) {
+    if (a.flags != b.flags) return false;
+    if (a.maximum_size != b.maximum_size) return false;
+
+    for(int i = 0; i < 36; ++i)
+        if (a.name[i] != b.name[i]) return false;
+
+    return true;
+}
+
+bool metadataio::operator==(const LpMetadataExtent &a, const LpMetadataExtent &b) {
+    if (a.num_sectors != b.num_sectors) return false;
+    if (a.target_type != b.target_type) return false;
+    if (a.target_data != b.target_data) return false;
+    if (a.target_source != b.target_source) return false;
+    return true;
+}
+
+bool metadataio::operator==(const LpMetadataPartition &a, const LpMetadataPartition &b){
+    if (a.attributes != b.attributes) return false;
+    if (a.first_extent_index != b.first_extent_index) return false;
+    if (a.num_extents != b.num_extents) return false;
+    if (a.group_index != b.group_index) return false;
+
+    for(int i = 0; i < 36; ++i)
+        if (a.name[i] != b.name[i]) return false;
+
+    return true;
+}
+
+bool metadataio::operator==(const LpMetadataTableDescriptor &a, const LpMetadataTableDescriptor &b) {
+    if (a.offset != b.offset) return false;
+    if (a.num_entries != b.num_entries) return false;
+    if (a.entry_size != b.entry_size) return false;
+    return true;
+}
+
+bool metadataio::operator==(const LpMetadataGeometry &a, const LpMetadataGeometry &b) {
+    if (a.magic != b.magic) return false;
+    if (a.struct_size != b.struct_size) return false;
+    if (a.metadata_max_size != b.metadata_max_size) return false;
+    if (a.metadata_slot_count != b.metadata_slot_count) return false;
+    if (a.logical_block_size != b.logical_block_size) return false;
+
+    for(int i = 0; i < 32; ++i)
+        if (a.checksum[i] != b.checksum[i]) return false;
+
+    return true;
+}
+
+bool metadataio::operator==(const LpMetadataHeader &a, const LpMetadataHeader &b) {
+    if (a.magic != b.magic) return false;
+    if (a.major_version != b.major_version) return false;
+    if (a.minor_version != b.minor_version) return false;
+    if (a.header_size != b.header_size) return false;
+    if (a.tables_size != b.tables_size) return false;
+    if (!(a.partitions == b.partitions)) return false;
+    if (!(a.extents == b.extents)) return false;
+    if (!(a.groups == b.groups)) return false;
+    if (!(a.block_devices == b.block_devices)) return false;
+    if (a.flags != b.flags) return false;
+
+    for(int i = 0; i < 32; ++i)
+        if (a.header_checksum[i] != b.header_checksum[i]) return false;
+
+    for(int i = 0; i < 32; ++i)
+        if (a.tables_checksum[i] != b.tables_checksum[i]) return false;
+
+    for(int i = 0; i < 124; ++i)
+        if (a.reserved[i] != b.reserved[i]) return false;
+
+    return true;
+}
+
+bool metadataio::operator==(const android::fs_mgr::LpMetadata &a, const android::fs_mgr::LpMetadata &b) {
+    if (!(a.geometry == b.geometry)) return false;
+    if (!(a.header == b.header)) return false;
+
+    if (a.partitions.size() != b.partitions.size()) return false;
+
+    for(size_t i = 0; i < a.partitions.size(); ++i)
+        if (!(a.partitions[i] == b.partitions[i])) return false;
+
+    if (a.extents.size() != b.extents.size()) return false;
+
+    for(size_t i = 0; i < a.extents.size(); ++i)
+        if (!(a.extents[i] == b.extents[i])) return false;
+
+    if (a.groups.size() != b.groups.size()) return false;
+
+    for(size_t i = 0; i < a.groups.size(); ++i)
+        if (!(a.groups[i] == b.groups[i])) return false;
+
+    if (a.block_devices.size() != b.block_devices.size()) return false;
+
+    for(size_t i = 0; i < a.block_devices.size(); ++i)
+        if (!(a.block_devices[i] == b.block_devices[i])) return false;
+
+    return true;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const LpMetadataBlockDevice &m) {
+    s << m.first_logical_sector << " "
+        << m.alignment << " "
+        << m.alignment_offset << " "
+        << m.size << " "
+        << m.flags << " ";
+
+    for(int i = 0; i < 36; ++i)
+        s << static_cast<uint64_t>(m.partition_name[i]) << " ";
+
+    return s;
+}
+
+std::istream& metadataio::operator>>(std::istream &s, LpMetadataBlockDevice &m) {
+    uint64_t first_logical_sector, size, alignment, alignment_offset, flags, tmp;
+
+    s >> first_logical_sector >> alignment >> alignment_offset >> size >> flags;
+
+    for(int i = 0; i < 36; ++i) {
+        s >> tmp;
+        m.partition_name[i] = tmp;
+    }
+
+    m.first_logical_sector = first_logical_sector;
+    m.alignment = alignment;
+    m.alignment_offset = alignment_offset;
+    m.size = size;
+    m.flags = flags;
+
+    return s;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const LpMetadataPartitionGroup &m) {
+    s << m.flags << " "
+        << m.maximum_size << " ";
+
+    for(int i = 0; i < 36; ++i)
+        s << static_cast<uint64_t>(m.name[i]) << " ";
+
+    return s;
+}
+
+std::istream& metadataio::operator>>(std::istream &s, LpMetadataPartitionGroup &m) {
+    uint64_t flags, maximum_size, tmp;
+
+    s >> flags >> maximum_size;
+
+    for(int i = 0; i < 36; ++i) {
+        s >> tmp;
+        m.name[i] = tmp;
+    }
+
+    m.flags = flags;
+    m.maximum_size = maximum_size;
+
+    return s;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const LpMetadataExtent &m) {
+    return s << m.num_sectors << " "
+        << m.target_type << " "
+        << m.target_data << " "
+        << m.target_source << " ";
+}
+
+std::istream& metadataio::operator>>(std::istream &s, LpMetadataExtent &m) {
+    uint64_t num_sectors, target_data, target_type, target_source;
+
+    s >> num_sectors >> target_type >> target_data >> target_source;
+
+    m.num_sectors = num_sectors;
+    m.target_type = target_type;
+    m.target_data = target_data;
+    m.target_source = target_source;
+
+    return s;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const LpMetadataPartition &m) {
+    s << m.attributes << " "
+        << m.first_extent_index << " "
+        << m.num_extents << " "
+        << m.group_index << " ";
+
+    for(int i = 0; i < 36; ++i)
+        s << static_cast<uint64_t>(m.name[i]) << " ";
+
+    return s;
+}
+
+std::istream& metadataio::operator>>(std::istream &s, LpMetadataPartition &m) {
+    uint64_t attributes, first_extent_index, num_extents, group_index, tmp;
+
+    s >> attributes >> first_extent_index >> num_extents >> group_index;
+
+    for(int i = 0; i < 36; ++i) {
+        s >> tmp;
+        m.name[i] = tmp;
+    }
+
+    m.attributes = attributes;
+    m.first_extent_index = first_extent_index;
+    m.num_extents = num_extents;
+    m.group_index = group_index;
+
+    return s;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const LpMetadataTableDescriptor &m) {
+    return s << m.offset << " "
+        << m.num_entries << " "
+        << m.entry_size << " ";
+}
+
+std::istream& metadataio::operator>>(std::istream &s, LpMetadataTableDescriptor &m) {
+    uint64_t offset, num_entries, entry_size;
+
+    s >> offset >> num_entries >> entry_size;
+
+    m.offset = offset;
+    m.num_entries = num_entries;
+    m.entry_size = entry_size;
+
+    return s;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const LpMetadataHeader &m) {
+    s << m.magic << " "
+        << m.major_version << " "
+        << m.minor_version << " "
+        << m.header_size << " "
+        << m.tables_size << " "
+        << m.flags << " "
+        << m.partitions << " "
+        << m.extents << " "
+        << m.groups << " "
+        << m.block_devices << " ";
+
+    for(int i = 0; i < 32; ++i)
+        s << static_cast<uint64_t>(m.header_checksum[i]) << " ";
+
+    for(int i = 0; i < 32; ++i)
+        s << static_cast<uint64_t>(m.tables_checksum[i]) << " ";
+
+    for(int i = 0; i < 124; ++i)
+        s << static_cast<uint64_t>(m.reserved[i]) << " ";
+
+    return s;
+}
+
+std::istream& metadataio::operator>>(std::istream &s, LpMetadataHeader &m) {
+    uint64_t magic, header_size, tables_size, flags, major_version, minor_version, tmp;
+
+    s >> magic >> major_version >> minor_version
+      >> header_size >> tables_size >> flags
+      >> m.partitions >> m.extents >> m.groups
+      >> m.block_devices;
+
+    for(int i = 0; i < 32; ++i) {
+        s >> tmp;
+        m.header_checksum[i] = tmp;
+    }
+
+    for(int i = 0; i < 32; ++i) {
+        s >> tmp;
+        m.tables_checksum[i] = tmp;
+    }
+
+    for(int i = 0; i < 124; ++i) {
+        s >> tmp;
+        m.reserved[i] = tmp;
+    }
+
+    m.magic = magic;
+    m.header_size = header_size;
+    m.tables_size = tables_size;
+    m.flags = flags;
+    m.major_version = major_version;
+    m.minor_version = minor_version;
+
+    return s;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const LpMetadataGeometry &m) {
+    s << m.magic << " "
+        << m.struct_size << " "
+        << m.metadata_max_size << " "
+        << m.metadata_slot_count << " "
+        << m.logical_block_size << " ";
+
+    for(int i = 0; i < 32; ++i)
+        s << static_cast<uint64_t>(m.checksum[i]) << " ";
+
+   return s;
+}
+
+std::istream& metadataio::operator>>(std::istream &s, LpMetadataGeometry &m) {
+    uint64_t magic, struct_size, metadata_max_size, metadata_slot_count, logical_block_size, tmp;
+
+    s >> magic >> struct_size >> metadata_max_size
+      >> metadata_slot_count >> logical_block_size;
+
+    for(int i = 0; i < 32; ++i) {
+        s >> tmp;
+        m.checksum[i] = tmp;
+    }
+
+    m.magic = magic;
+    m.struct_size = struct_size;
+    m.metadata_max_size = metadata_max_size;
+    m.metadata_slot_count = metadata_slot_count;
+    m.logical_block_size = logical_block_size;
+
+   return s;
+}
+
+std::ostream& metadataio::operator<<(std::ostream &s, const android::fs_mgr::LpMetadata &m) {
+    s << m.geometry << " " << m.header << " ";
+
+    s << m.partitions.size() << " ";
+
+    for(auto &el : m.partitions)
+        s << el << " ";
+
+    s << m.extents.size() << " ";
+
+    for(auto &el : m.extents)
+        s << el << " ";
+
+    s << m.groups.size() << " ";
+
+    for(auto &el : m.groups)
+        s << el << " ";
+
+    s << m.block_devices.size() << " ";
+
+    for(auto &el : m.block_devices)
+        s << el << " ";
+
+    return s;
+}
+
+std::istream& metadataio::operator>>(std::istream &s, android::fs_mgr::LpMetadata &m) {
+    uint64_t n_partitions, n_extents, n_groups, n_devices;
+    LpMetadataPartition partition;
+    LpMetadataExtent extent;
+    LpMetadataPartitionGroup group;
+    LpMetadataBlockDevice block_device;
+
+    s >> m.geometry >> m.header >> n_partitions;
+
+    for(uint64_t i = 0; i < n_partitions; ++i) {
+        s >> partition;
+        m.partitions.push_back(partition);
+    }
+
+    s >> n_extents;
+
+    for(uint64_t i = 0; i < n_extents; ++i) {
+        s >> extent;
+        m.extents.push_back(extent);
+    }
+
+    s >> n_groups;
+
+    for(uint64_t i = 0; i < n_groups; ++i) {
+        s >> group;
+        m.groups.push_back(group);
+    }
+
+    s >> n_devices;
+
+    for(uint64_t i = 0; i < n_devices; ++i) {
+        s >> block_device;
+        m.block_devices.push_back(block_device);
+    }
+
+    return s;
+}
\ No newline at end of file
diff --git a/src/parse-dynparts/test/metadataio.h b/src/parse-dynparts/test/metadataio.h
new file mode 100644 (file)
index 0000000..28e14ca
--- /dev/null
@@ -0,0 +1,39 @@
+/* Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: MIT */
+
+#ifndef _METADATAIO_H_
+#define _METADATAIO_H_
+
+#include <liblp/liblp.h>
+#include <iostream>
+
+namespace metadataio {
+    bool operator==(const LpMetadataBlockDevice &a, const LpMetadataBlockDevice &b);
+    bool operator==(const LpMetadataPartitionGroup &a, const LpMetadataPartitionGroup &b);
+    bool operator==(const LpMetadataExtent &a, const LpMetadataExtent &b);
+    bool operator==(const LpMetadataPartition &a, const LpMetadataPartition &b);
+    bool operator==(const LpMetadataTableDescriptor &a, const LpMetadataTableDescriptor &b);
+    bool operator==(const LpMetadataGeometry &a, const LpMetadataGeometry &b);
+    bool operator==(const LpMetadataHeader &a, const LpMetadataHeader &b);
+    bool operator==(const android::fs_mgr::LpMetadata &a, const android::fs_mgr::LpMetadata &b);
+
+    std::ostream& operator<<(std::ostream &s, const LpMetadataBlockDevice &m);
+    std::ostream& operator<<(std::ostream &s, const LpMetadataPartitionGroup &m);
+    std::ostream& operator<<(std::ostream &s, const LpMetadataExtent &m);
+    std::ostream& operator<<(std::ostream &s, const LpMetadataPartition &m);
+    std::ostream& operator<<(std::ostream &s, const LpMetadataTableDescriptor &m);
+    std::ostream& operator<<(std::ostream &s, const LpMetadataGeometry &m);
+    std::ostream& operator<<(std::ostream &s, const LpMetadataHeader &m);
+    std::ostream& operator<<(std::ostream &s, const android::fs_mgr::LpMetadata &m);
+
+    std::istream& operator>>(std::istream &s, LpMetadataBlockDevice &m);
+    std::istream& operator>>(std::istream &s, LpMetadataPartitionGroup &m);
+    std::istream& operator>>(std::istream &s, LpMetadataExtent &m);
+    std::istream& operator>>(std::istream &s, LpMetadataPartition &m);
+    std::istream& operator>>(std::istream &s, LpMetadataTableDescriptor &m);
+    std::istream& operator>>(std::istream &s, LpMetadataGeometry &m);
+    std::istream& operator>>(std::istream &s, LpMetadataHeader &m);
+    std::istream& operator>>(std::istream &s, android::fs_mgr::LpMetadata &m);
+}
+
+#endif // _METADATAIO_H_
\ No newline at end of file
diff --git a/src/parse-dynparts/test/super_dump.cpp b/src/parse-dynparts/test/super_dump.cpp
new file mode 100644 (file)
index 0000000..e61f2da
--- /dev/null
@@ -0,0 +1,28 @@
+/* Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: MIT */
+
+#include <iostream>
+#include <liblp/liblp.h>
+
+#include "metadataio.h"
+
+using namespace std;
+using namespace metadataio;
+
+int main(int argc, char **argv) {
+    if(argc != 2) {
+        cout << "Usage: " << argv[0] << " path_to_super" << endl;
+        return 1;
+    }
+
+    auto metadata = android::fs_mgr::ReadMetadata(argv[1], 0);
+
+    if (!metadata) {
+        cerr << "Failed to parse metadata from \"" << argv[1] << "\"" << endl;
+        return 1;
+    }
+
+    cout << *metadata << endl;
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/parse-dynparts/test/test.cpp b/src/parse-dynparts/test/test.cpp
new file mode 100644 (file)
index 0000000..5dce862
--- /dev/null
@@ -0,0 +1,940 @@
+/* Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * SPDX-License-Identifier: MIT
+ *
+ * parse-dynparts relies only on LpMetadata.partitions and LpMetadata.extents
+ * fields. That's why it is only needed to fill these specific fields of an
+ * empty LpMetadata structure in order to generate output for tests.
+ *
+ * To avoid any errors due to manual creation of LpMetadata, lpmake was used
+ * to create real super images having certain partition and group layouts.
+ * Next, values of certain fields were read in a debugger and used to create
+ * test LpMetadata structures.
+ *
+ * Each test contains metadata_str variable which contains full dump of a
+ * super image created using stream operators from metadataio.h. This is done
+ * to allow creation of a full LpMetadata structure which can be used to create
+ * real super image corresponding to a specific test. */
+
+#include <iostream>
+#include <gtest/gtest.h>
+#include <string>
+#include "../lib.hpp"
+
+using namespace android::fs_mgr;
+
+#ifdef METADATA_IO
+#include <sstream>
+#include "metadataio.h"
+using namespace metadataio;
+
+void readMetadata(LpMetadata &m, std::string s) {
+    std::stringstream metadata_stream;
+    metadata_stream << s;
+    metadata_stream >> m;
+}
+#endif
+
+#define PART_SIZE 5 // MB
+
+void addPartitions(LpMetadata &metadata, int number_of_groups, int number_of_parts) {
+    LpMetadataPartition p;
+    LpMetadataExtent e;
+
+    for(int group = 0; group < number_of_groups; ++group) {
+        std::string group_suffix =  "_";
+        group_suffix += ('a' + group);
+
+        for(int part = 0; part < number_of_parts; ++part) {
+            int idx = group * number_of_parts + part;
+            p.attributes = 0;
+            p.first_extent_index = idx;
+            p.num_extents = 1;
+            p.group_index = group + 1;
+            std::string part_name = "part_" + std::to_string(part + 1) + group_suffix;
+            strncpy(p.name, part_name.c_str(), 36);
+
+            metadata.partitions.push_back(p);
+
+            e.num_sectors = 10240;
+            e.target_type = 0;
+            // add offset equall to idx * PART_SIZE as a number of 512B sectors (1024 * 1024 / 512)
+            e.target_data = 2048 + idx * PART_SIZE * 2048;
+            e.target_source = 0;
+
+            metadata.extents.push_back(e);
+        }
+    }
+}
+
+// Empty metadata
+TEST(ParseDynpartsTest, EmptyMetadata) {
+    LpMetadata metadata {};
+    std::ostringstream messages;
+    EXPECT_EQ(go(metadata, false, "test", messages), "");
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), "");
+    EXPECT_EQ(messages.str(), "");
+}
+
+// One group and one partition
+TEST(ParseDynpartsTest, GroupAOnePart) {
+    LpMetadata metadata;
+
+#ifdef METADATA_IO
+    // super_1.img
+    std::string metadata_str = "1634485351 52 65536 1 4096 120 75 47 222 48 213 6 78 122 230 66 214 51 226 131 122 78 100 23 86 88 86 36 222 17 7 236 187 122 127 108 9  1095520304 10 0 128 236 0 0 1 52  52 1 24  76 2 48  172 1 64  79 119 146 123 183 233 167 221 228 70 230 58 47 182 200 165 64 233 92 109 201 251 116 215 255 12 1 196 85 200 104 222 247 121 40 1 114 204 108 169 136 120 22 181 181 235 50 96 48 125 91 42 101 198 44 159 40 40 172 246 180 45 102 172 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 10240 0 2048 0  2 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5242880 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 6291456 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+   addPartitions(metadata, 1, 1);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// One group and two partitions
+TEST(ParseDynpartsTest, GroupATwoParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_2.img
+    std::string metadata_str = "1634485351 52 65536 1 4096 120 75 47 222 48 213 6 78 122 230 66 214 51 226 131 122 78 100 23 86 88 86 36 222 17 7 236 187 122 127 108 9  1095520304 10 0 128 312 0 0 2 52  104 2 24  152 2 48  248 1 64  234 50 126 183 222 226 91 93 28 243 169 49 212 81 150 149 179 92 156 241 115 36 233 141 148 131 230 125 185 16 247 227 246 93 123 112 111 101 135 103 119 224 141 134 251 57 64 128 15 228 200 224 70 34 13 182 239 30 119 67 54 53 127 102 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  2 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  2 10240 0 2048 0  10240 0 12288 0  2 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 10485760 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 11534336 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  ";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 1, 2);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// One group with three partitions
+TEST(ParseDynpartsTest, GroupAThreeParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_3.img
+    std::string metadata_str = "1634485351 52 65536 1 4096 120 75 47 222 48 213 6 78 122 230 66 214 51 226 131 122 78 100 23 86 88 86 36 222 17 7 236 187 122 127 108 9  1095520304 10 0 128 388 0 0 3 52  156 3 24  228 2 48  324 1 64  50 128 136 94 112 62 194 47 58 27 254 167 231 39 117 233 120 245 153 17 230 74 195 158 219 181 195 22 245 118 29 145 63 94 243 41 255 150 90 252 99 109 75 92 55 74 9 197 186 247 143 19 32 184 219 58 97 101 37 225 189 243 133 112 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  3 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  3 10240 0 2048 0  10240 0 12288 0  10240 0 22528 0  2 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 16777216 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  ";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 1, 3);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288;part_3_a,,,rw,0 10240 linear test 22528";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288\npart_3_a 0 10240 linear test 22528";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// One group with four partitions
+TEST(ParseDynpartsTest, GroupAFourParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_4.img
+    std::string metadata_str = "1634485351 52 65536 1 4096 120 75 47 222 48 213 6 78 122 230 66 214 51 226 131 122 78 100 23 86 88 86 36 222 17 7 236 187 122 127 108 9  1095520304 10 0 128 464 0 0 4 52  208 4 24  304 2 48  400 1 64  155 220 87 185 23 251 126 142 219 228 253 211 79 90 28 224 57 126 231 190 248 223 25 225 72 199 59 106 57 205 60 213 118 78 28 69 115 211 73 136 129 190 152 180 159 235 110 226 196 144 220 68 5 94 91 193 145 199 98 31 44 84 122 76 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  4 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  4 10240 0 2048 0  10240 0 12288 0  10240 0 22528 0  10240 0 32768 0  2 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 22020096 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 1, 4);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288;part_3_a,,,rw,0 10240 linear test 22528;part_4_a,,,rw,0 10240 linear test 32768";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288\npart_3_a 0 10240 linear test 22528\npart_4_a 0 10240 linear test 32768";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups with one partition each
+TEST(ParseDynpartsTest, GroupAOnePartGroupBOnePart) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_5.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 360 0 0 2 52  104 2 24  152 3 48  296 1 64  142 38 219 208 156 164 215 68 103 134 165 40 64 23 126 197 223 167 68 151 23 207 114 219 153 32 233 95 156 6 124 138 204 6 90 104 78 237 218 3 93 171 2 26 199 239 20 254 34 135 133 12 208 241 49 201 50 111 119 119 142 110 16 192 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  2 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  2 10240 0 2048 0  10240 0 12288 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5242880 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5242880 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 11534336 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 1);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_1_b,,,rw,0 10240 linear test 12288";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_1_b 0 10240 linear test 12288";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups with two partitions each
+TEST(ParseDynpartsTest, GroupATwoPartsGroupBTwoParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_6.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 512 0 0 4 52  208 4 24  304 3 48  448 1 64  161 234 225 151 214 94 179 110 2 0 233 234 4 104 28 117 218 103 195 188 66 118 247 36 180 140 15 130 86 252 67 97 115 0 139 231 64 64 16 79 80 219 28 134 12 81 237 114 150 253 218 198 222 11 73 134 255 98 56 133 250 245 232 168 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  4 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  4 10240 0 2048 0  10240 0 12288 0  10240 0 22528 0  10240 0 32768 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 10485760 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 10485760 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 22020096 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 2);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288;part_1_b,,,rw,0 10240 linear test 22528;part_2_b,,,rw,0 10240 linear test 32768";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288\npart_1_b 0 10240 linear test 22528\npart_2_b 0 10240 linear test 32768";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups with three partitions each
+TEST(ParseDynpartsTest, GroupAThreePartsGroupBThreeParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_7.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 664 0 0 6 52  312 6 24  456 3 48  600 1 64  69 42 160 36 106 231 230 54 104 182 243 64 70 203 84 13 147 38 249 74 127 205 226 59 138 82 57 100 86 160 64 74 211 56 108 6 122 167 23 172 28 44 13 73 223 76 115 107 5 151 86 171 104 128 132 48 208 174 108 126 12 62 205 69 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  6 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  6 10240 0 2048 0  10240 0 12288 0  10240 0 22528 0  10240 0 32768 0  10240 0 43008 0  10240 0 53248 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 32505856 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 3);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288;part_3_a,,,rw,0 10240 linear test 22528;part_1_b,,,rw,0 10240 linear test 32768;part_2_b,,,rw,0 10240 linear test 43008;part_3_b,,,rw,0 10240 linear test 53248";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288\npart_3_a 0 10240 linear test 22528\npart_1_b 0 10240 linear test 32768\npart_2_b 0 10240 linear test 43008\npart_3_b 0 10240 linear test 53248";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups with four partitions each
+TEST(ParseDynpartsTest, GroupsAFourPartsGroupBFourParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_8.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 816 0 0 8 52  416 8 24  608 3 48  752 1 64  127 63 109 36 54 5 233 190 2 129 37 201 27 54 232 17 130 172 20 225 202 21 214 104 177 90 5 77 19 153 180 250 211 202 212 140 38 22 156 156 220 142 213 188 92 55 152 201 86 69 186 196 197 69 88 178 91 117 218 245 88 31 107 143 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  8 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 6 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 7 1 2 112 97 114 116 95 52 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  8 10240 0 2048 0  10240 0 12288 0  10240 0 22528 0  10240 0 32768 0  10240 0 43008 0  10240 0 53248 0  10240 0 63488 0  10240 0 73728 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 42991616 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 4);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288;part_3_a,,,rw,0 10240 linear test 22528;part_4_a,,,rw,0 10240 linear test 32768;part_1_b,,,rw,0 10240 linear test 43008;part_2_b,,,rw,0 10240 linear test 53248;part_3_b,,,rw,0 10240 linear test 63488;part_4_b,,,rw,0 10240 linear test 73728";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288\npart_3_a 0 10240 linear test 22528\npart_4_a 0 10240 linear test 32768\npart_1_b 0 10240 linear test 43008\npart_2_b 0 10240 linear test 53248\npart_3_b 0 10240 linear test 63488\npart_4_b 0 10240 linear test 73728";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// One group with four partitions; one partition consists of two extents
+TEST(ParseDynpartsTest, GroupAFourPartsOneHasTwoExtents) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_1.img based on super_3.img
+    std::string metadata_str = "1634485351 52 65536 1 4096 120 75 47 222 48 213 6 78 122 230 66 214 51 226 131 122 78 100 23 86 88 86 36 222 17 7 236 187 122 127 108 9  1095520304 10 0 128 488 0 0 4 52  208 5 24  328 2 48  424 1 64  182 115 169 159 30 122 130 132 245 100 53 140 175 182 14 165 119 221 226 206 237 134 81 0 178 176 36 162 210 243 169 188 97 79 116 70 101 55 92 119 186 140 75 200 30 166 205 45 223 0 110 5 194 237 47 150 98 82 236 182 154 161 98 31 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  4 0 0 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 2 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  5 10240 0 12288 0  2048 0 2048 0  2048 0 4096 0  6144 0 6144 0  4096 0 22528 0  2 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 16777216 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 1, 4);
+
+    strncpy(metadata.partitions[0].name, "part_2_a", 36);
+
+    strncpy(metadata.partitions[1].name, "part_1_a", 36);
+
+    metadata.partitions[3].num_extents = 2;
+
+    metadata.extents[0].target_data = 12288;
+
+    metadata.extents[1].num_sectors = 2048;
+    metadata.extents[1].target_data = 2048;
+
+    metadata.extents[2].num_sectors = 2048;
+    metadata.extents[2].target_data = 4096;
+
+    metadata.extents[3].num_sectors = 6144;
+    metadata.extents[3].target_data = 6144;
+
+    LpMetadataExtent e;
+    e.num_sectors = 4096;
+    e.target_type = 0;
+    e.target_data = 22528;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_2_a,,,rw,0 10240 linear test 12288;part_1_a,,,rw,0 2048 linear test 2048;part_3_a,,,rw,0 2048 linear test 4096;part_4_a,,,rw,0 6144 linear test 6144,6144 4096 linear test 22528";
+    std::string output_list_tables = "part_2_a 0 10240 linear test 12288\npart_1_a 0 2048 linear test 2048\npart_3_a 0 2048 linear test 4096\npart_4_a 0 6144 linear test 6144\\n6144 4096 linear test 22528";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// One group with five partitions; two partitions consist of two extents
+TEST(ParseDynpartsTest, GroupAFivePartsTwoHaveTwoExtents) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_2.img based on super_3.img
+    std::string metadata_str = "1634485351 52 65536 1 4096 120 75 47 222 48 213 6 78 122 230 66 214 51 226 131 122 78 100 23 86 88 86 36 222 17 7 236 187 122 127 108 9  1095520304 10 0 128 588 0 0 5 52  260 7 24  428 2 48  524 1 64  112 102 57 102 31 202 124 74 55 54 2 70 130 54 95 99 236 209 243 70 173 136 30 29 243 29 206 174 24 109 164 35 93 117 80 179 206 178 79 53 134 247 15 155 144 9 80 221 184 44 89 81 235 112 227 147 157 150 115 117 199 14 210 87 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  5 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 2 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 2 1 112 97 114 116 95 53 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  7 2048 0 2048 0  2048 0 4096 0  6144 0 6144 0  4096 0 22528 0  2048 0 12288 0  8192 0 14336 0  2048 0 26624 0  2 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 16777216 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 1, 5);
+
+    strncpy(metadata.partitions[1].name, "part_3_a", 36);
+
+    strncpy(metadata.partitions[2].name, "part_4_a", 36);
+    metadata.partitions[2].num_extents = 2;
+
+    strncpy(metadata.partitions[3].name, "part_2_a", 36);
+    metadata.partitions[3].first_extent_index = 4;
+
+    metadata.partitions[4].first_extent_index = 5;
+    metadata.partitions[4].num_extents = 2;
+
+    metadata.extents[0].num_sectors = 2048;
+    metadata.extents[0].target_data = 2048;
+
+    metadata.extents[1].num_sectors = 2048;
+    metadata.extents[1].target_data = 4096;
+
+    metadata.extents[2].num_sectors = 6144;
+    metadata.extents[2].target_data = 6144;
+
+    metadata.extents[3].num_sectors = 4096;
+    metadata.extents[3].target_data = 22528;
+
+    metadata.extents[4].num_sectors = 2048;
+    metadata.extents[4].target_data = 12288;
+
+    LpMetadataExtent e;
+    e.num_sectors = 8192;
+    e.target_type = 0;
+    e.target_data = 14336;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 2048;
+    e.target_type = 0;
+    e.target_data = 26624;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 2048 linear test 2048;part_3_a,,,rw,0 2048 linear test 4096;part_4_a,,,rw,0 6144 linear test 6144,6144 4096 linear test 22528;part_2_a,,,rw,0 2048 linear test 12288;part_5_a,,,rw,0 8192 linear test 14336,8192 2048 linear test 26624";
+    std::string output_list_tables = "part_1_a 0 2048 linear test 2048\npart_3_a 0 2048 linear test 4096\npart_4_a 0 6144 linear test 6144\\n6144 4096 linear test 22528\npart_2_a 0 2048 linear test 12288\npart_5_a 0 8192 linear test 14336\\n8192 2048 linear test 26624";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// One group with five partitions; one partition consist of three extents
+TEST(ParseDynpartsTest, GroupAFivePartsOneHasThreeExtents) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_3.img based on super_4.img
+    std::string metadata_str = "1634485351 52 65536 1 4096 120 75 47 222 48 213 6 78 122 230 66 214 51 226 131 122 78 100 23 86 88 86 36 222 17 7 236 187 122 127 108 9  1095520304 10 0 128 588 0 0 5 52  260 7 24  428 2 48  524 1 64  100 67 158 102 9 185 191 145 244 147 52 179 150 1 80 117 32 77 229 6 72 25 144 175 185 79 122 183 100 217 11 253 207 3 41 136 200 228 159 31 201 120 210 187 225 216 137 53 141 119 25 246 180 148 229 255 42 241 168 240 213 85 125 123 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  5 0 0 1 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 3 1 112 97 114 116 95 53 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  7 10240 0 32768 0  2048 0 22528 0  2048 0 12288 0  2048 0 2048 0  8192 0 4096 0  8192 0 14336 0  4096 0 24576 0  2 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 22020096 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 1, 5);
+
+    strncpy(metadata.partitions[0].name, "part_4_a", 36);
+
+    strncpy(metadata.partitions[1].name, "part_3_a", 36);
+
+    strncpy(metadata.partitions[2].name, "part_2_a", 36);
+
+    strncpy(metadata.partitions[3].name, "part_1_a", 36);
+
+    metadata.partitions[4].num_extents = 3;
+
+    metadata.extents[0].num_sectors = 10240;
+    metadata.extents[0].target_data = 32768;
+
+    metadata.extents[1].num_sectors = 2048;
+    metadata.extents[1].target_data = 22528;
+
+    metadata.extents[2].num_sectors = 2048;
+    metadata.extents[2].target_data = 12288;
+
+    metadata.extents[3].num_sectors = 2048;
+    metadata.extents[3].target_data = 2048;
+
+    metadata.extents[4].num_sectors = 8192;
+    metadata.extents[4].target_data = 4096;
+
+    LpMetadataExtent e;
+    e.num_sectors = 8192;
+    e.target_type = 0;
+    e.target_data = 14336;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 4096;
+    e.target_type = 0;
+    e.target_data = 24576;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_4_a,,,rw,0 10240 linear test 32768;part_3_a,,,rw,0 2048 linear test 22528;part_2_a,,,rw,0 2048 linear test 12288;part_1_a,,,rw,0 2048 linear test 2048;part_5_a,,,rw,0 8192 linear test 4096,8192 8192 linear test 14336,16384 4096 linear test 24576";
+    std::string output_list_tables = "part_4_a 0 10240 linear test 32768\npart_3_a 0 2048 linear test 22528\npart_2_a 0 2048 linear test 12288\npart_1_a 0 2048 linear test 2048\npart_5_a 0 8192 linear test 4096\\n8192 8192 linear test 14336\\n16384 4096 linear test 24576";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups; First has four partitions; One partition has two extents; Second group has three partitions
+TEST(ParseDynpartsTest, GroupAFourPartsOneHasTwoExtentsGroupBThreeParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_4.img based on super_7.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 764 0 0 7 52  364 8 24  556 3 48  700 1 64  198 39 137 113 53 13 241 29 164 16 135 70 50 179 146 18 33 232 109 204 190 81 254 153 145 166 196 17 225 79 207 64 253 84 240 176 19 187 121 118 144 122 225 243 137 10 28 31 158 220 17 207 29 208 4 94 10 63 195 9 70 67 163 121 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  7 0 0 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 6 2 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  8 10240 0 12288 0  10240 0 32768 0  10240 0 43008 0  10240 0 53248 0  2048 0 2048 0  2048 0 4096 0  6144 0 6144 0  4096 0 22528 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 32505856 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 3);
+
+    strncpy(metadata.partitions[0].name, "part_2_a", 36);
+
+    strncpy(metadata.partitions[1].name, "part_1_b", 36);
+    metadata.partitions[1].group_index = 2;
+
+    strncpy(metadata.partitions[2].name, "part_2_b", 36);
+    metadata.partitions[2].group_index = 2;
+
+    strncpy(metadata.partitions[3].name, "part_3_b", 36);
+
+    strncpy(metadata.partitions[4].name, "part_1_a", 36);
+    metadata.partitions[4].group_index = 1;
+
+    strncpy(metadata.partitions[5].name, "part_3_a", 36);
+    metadata.partitions[5].group_index = 1;
+
+    LpMetadataPartition p;
+    p.attributes = 0;
+    p.first_extent_index = 6;
+    p.num_extents = 2;
+    p.group_index = 1;
+    strncpy(p.name, "part_4_a", 36);
+
+    metadata.partitions.push_back(p);
+
+    metadata.extents[0].num_sectors = 10240;
+    metadata.extents[0].target_data = 12288;
+
+    metadata.extents[1].num_sectors = 10240;
+    metadata.extents[1].target_data = 32768;
+
+    metadata.extents[2].num_sectors = 10240;
+    metadata.extents[2].target_data = 43008;
+
+    metadata.extents[3].num_sectors = 10240;
+    metadata.extents[3].target_data = 53248;
+
+    metadata.extents[4].num_sectors = 2048;
+    metadata.extents[4].target_data = 2048;
+
+    metadata.extents[5].num_sectors = 2048;
+    metadata.extents[5].target_data = 4096;
+
+    LpMetadataExtent e;
+    e.num_sectors = 6144;
+    e.target_type = 0;
+    e.target_data = 6144;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 4096;
+    e.target_type = 0;
+    e.target_data = 22528;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_2_a,,,rw,0 10240 linear test 12288;part_1_b,,,rw,0 10240 linear test 32768;part_2_b,,,rw,0 10240 linear test 43008;part_3_b,,,rw,0 10240 linear test 53248;part_1_a,,,rw,0 2048 linear test 2048;part_3_a,,,rw,0 2048 linear test 4096;part_4_a,,,rw,0 6144 linear test 6144,6144 4096 linear test 22528";
+    std::string output_list_tables = "part_2_a 0 10240 linear test 12288\npart_1_b 0 10240 linear test 32768\npart_2_b 0 10240 linear test 43008\npart_3_b 0 10240 linear test 53248\npart_1_a 0 2048 linear test 2048\npart_3_a 0 2048 linear test 4096\npart_4_a 0 6144 linear test 6144\\n6144 4096 linear test 22528";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups; First has three partitions; Second group has four partitions; One partition has two extents
+TEST(ParseDynpartsTest, GroupAThreePartsGroupBFourPartsOneHasTwoExtents) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_5.img based on super_7.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 764 0 0 7 52  364 8 24  556 3 48  700 1 64  172 34 125 244 217 227 56 198 196 57 66 159 75 5 75 26 201 151 120 220 210 224 106 22 229 179 139 59 7 70 91 253 27 204 4 10 117 183 48 238 0 228 135 248 206 221 161 77 18 220 250 173 233 91 10 135 107 128 233 70 149 225 147 72 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  7 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 6 2 2 112 97 114 116 95 52 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  8 10240 0 2048 0  10240 0 12288 0  10240 0 22528 0  10240 0 43008 0  2048 0 32768 0  2048 0 34816 0  6144 0 36864 0  4096 0 53248 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 32505856 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 3);
+
+    strncpy(metadata.partitions[3].name, "part_2_b", 36);
+
+    strncpy(metadata.partitions[4].name, "part_1_b", 36);
+
+    LpMetadataPartition p;
+    p.attributes = 0;
+    p.first_extent_index = 6;
+    p.num_extents = 2;
+    p.group_index = 2;
+    strncpy(p.name, "part_4_b", 36);
+
+    metadata.partitions.push_back(p);
+
+    metadata.extents[0].num_sectors = 10240;
+    metadata.extents[0].target_data = 2048;
+
+    metadata.extents[1].num_sectors = 10240;
+    metadata.extents[1].target_data = 12288;
+
+    metadata.extents[2].num_sectors = 10240;
+    metadata.extents[2].target_data = 22528;
+
+    metadata.extents[3].num_sectors = 10240;
+    metadata.extents[3].target_data = 43008;
+
+    metadata.extents[4].num_sectors = 2048;
+    metadata.extents[4].target_data = 32768;
+
+    metadata.extents[5].num_sectors = 2048;
+    metadata.extents[5].target_data = 34816;
+
+    LpMetadataExtent e;
+    e.num_sectors = 6144;
+    e.target_type = 0;
+    e.target_data = 36864;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 4096;
+    e.target_type = 0;
+    e.target_data = 53248;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288;part_3_a,,,rw,0 10240 linear test 22528;part_2_b,,,rw,0 10240 linear test 43008;part_1_b,,,rw,0 2048 linear test 32768;part_3_b,,,rw,0 2048 linear test 34816;part_4_b,,,rw,0 6144 linear test 36864,6144 4096 linear test 53248";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288\npart_3_a 0 10240 linear test 22528\npart_2_b 0 10240 linear test 43008\npart_1_b 0 2048 linear test 32768\npart_3_b 0 2048 linear test 34816\npart_4_b 0 6144 linear test 36864\\n6144 4096 linear test 53248";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups; First has four partitions; One partition has two extents; Second group has four partitions
+TEST(ParseDynpartsTest, GroupAFourPartsOneHasTwoExtentsGroupBFourParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_6.img based on super_7.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 840 0 0 8 52  416 9 24  632 3 48  776 1 64  21 233 41 204 110 189 99 227 103 32 29 115 169 0 49 156 54 147 216 58 13 63 135 111 149 144 250 205 58 96 217 234 8 65 18 137 114 182 215 45 54 149 243 40 233 128 136 53 186 77 19 141 140 131 83 185 58 72 178 55 250 98 181 140 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  8 0 0 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 2 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 6 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 7 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 8 1 2 112 97 114 116 95 52 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  9 10240 0 12288 0  10240 0 43008 0  2048 0 2048 0  2048 0 4096 0  6144 0 6144 0  4096 0 22528 0  2048 0 26624 0  2048 0 28672 0  10240 0 30720 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 15728640 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 32505856 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 4);
+
+    strncpy(metadata.partitions[0].name, "part_2_a", 36);
+
+    strncpy(metadata.partitions[1].name, "part_2_b", 36);
+    metadata.partitions[1].group_index = 2;
+
+    strncpy(metadata.partitions[2].name, "part_1_a", 36);
+
+    strncpy(metadata.partitions[3].name, "part_3_a", 36);
+
+    strncpy(metadata.partitions[4].name, "part_4_a", 36);
+    metadata.partitions[4].group_index = 1;
+    metadata.partitions[4].num_extents = 2;
+
+    strncpy(metadata.partitions[5].name, "part_1_b", 36);
+    metadata.partitions[5].first_extent_index = 6;
+
+    metadata.partitions[6].first_extent_index = 7;
+
+    metadata.partitions[7].first_extent_index = 8;
+
+    metadata.extents[0].num_sectors = 10240;
+    metadata.extents[0].target_data = 12288;
+
+    metadata.extents[1].num_sectors = 10240;
+    metadata.extents[1].target_data = 43008;
+
+    metadata.extents[2].num_sectors = 2048;
+    metadata.extents[2].target_data = 2048;
+
+    metadata.extents[3].num_sectors = 2048;
+    metadata.extents[3].target_data = 4096;
+
+    metadata.extents[4].num_sectors = 6144;
+    metadata.extents[4].target_data = 6144;
+
+    metadata.extents[5].num_sectors = 4096;
+    metadata.extents[5].target_data = 22528;
+
+    metadata.extents[6].num_sectors = 2048;
+    metadata.extents[6].target_data = 26624;
+
+    metadata.extents[7].num_sectors = 2048;
+    metadata.extents[7].target_data = 28672;
+
+    LpMetadataExtent e;
+    e.num_sectors = 10240;
+    e.target_type = 0;
+    e.target_data = 30720;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_2_a,,,rw,0 10240 linear test 12288;part_2_b,,,rw,0 10240 linear test 43008;part_1_a,,,rw,0 2048 linear test 2048;part_3_a,,,rw,0 2048 linear test 4096;part_4_a,,,rw,0 6144 linear test 6144,6144 4096 linear test 22528;part_1_b,,,rw,0 2048 linear test 26624;part_3_b,,,rw,0 2048 linear test 28672;part_4_b,,,rw,0 10240 linear test 30720";
+    std::string output_list_tables = "part_2_a 0 10240 linear test 12288\npart_2_b 0 10240 linear test 43008\npart_1_a 0 2048 linear test 2048\npart_3_a 0 2048 linear test 4096\npart_4_a 0 6144 linear test 6144\\n6144 4096 linear test 22528\npart_1_b 0 2048 linear test 26624\npart_3_b 0 2048 linear test 28672\npart_4_b 0 10240 linear test 30720";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups; First has five partitions; One partition has three extents; Second group has four partitions
+TEST(ParseDynpartsTest, GroupAFivePartsOneHasThreeExtentsGroupBFourParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_7.img based on super_8.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 940 0 0 9 52  468 11 24  732 3 48  876 1 64  38 157 64 86 69 136 89 208 53 119 189 46 238 250 109 107 35 163 2 98 153 127 97 131 38 164 29 163 223 90 60 45 156 188 72 154 94 33 123 149 8 138 35 29 238 233 46 251 215 121 182 44 187 60 141 14 210 154 102 61 210 233 201 246 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  9 0 0 1 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 2 112 97 114 116 95 52 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 6 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 7 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 8 3 1 112 97 114 116 95 53 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  11 10240 0 32768 0  10240 0 43008 0  10240 0 53248 0  10240 0 63488 0  10240 0 73728 0  2048 0 22528 0  2048 0 12288 0  2048 0 2048 0  8192 0 4096 0  8192 0 14336 0  4096 0 24576 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 42991616 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 4);
+
+    strncpy(metadata.partitions[0].name, "part_4_a", 36);
+
+    strncpy(metadata.partitions[1].name, "part_1_b", 36);
+    metadata.partitions[1].group_index = 2;
+
+    strncpy(metadata.partitions[2].name, "part_2_b", 36);
+    metadata.partitions[2].group_index = 2;
+
+    strncpy(metadata.partitions[3].name, "part_3_b", 36);
+    metadata.partitions[3].group_index = 2;
+
+    strncpy(metadata.partitions[4].name, "part_4_b", 36);
+
+    strncpy(metadata.partitions[5].name, "part_3_a", 36);
+    metadata.partitions[5].group_index = 1;
+
+    strncpy(metadata.partitions[6].name, "part_2_a", 36);
+    metadata.partitions[6].group_index = 1;
+
+    strncpy(metadata.partitions[7].name, "part_1_a", 36);
+    metadata.partitions[7].group_index = 1;
+
+    LpMetadataPartition p;
+    p.attributes = 0;
+    p.first_extent_index = 8;
+    p.num_extents = 3;
+    p.group_index = 1;
+    strncpy(p.name, "part_5_a", 36);
+
+    metadata.partitions.push_back(p);
+
+    metadata.extents[0].num_sectors = 10240;
+    metadata.extents[0].target_data = 32768;
+
+    metadata.extents[1].num_sectors = 10240;
+    metadata.extents[1].target_data = 43008;
+
+    metadata.extents[2].num_sectors = 10240;
+    metadata.extents[2].target_data = 53248;
+
+    metadata.extents[3].num_sectors = 10240;
+    metadata.extents[3].target_data = 63488;
+
+    metadata.extents[4].num_sectors = 10240;
+    metadata.extents[4].target_data = 73728;
+
+    metadata.extents[5].num_sectors = 2048;
+    metadata.extents[5].target_data = 22528;
+
+    metadata.extents[6].num_sectors = 2048;
+    metadata.extents[6].target_data = 12288;
+
+    metadata.extents[7].num_sectors = 2048;
+    metadata.extents[7].target_data = 2048;
+
+    LpMetadataExtent e;
+    e.num_sectors = 8192;
+    e.target_type = 0;
+    e.target_data = 4096;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 8192;
+    e.target_type = 0;
+    e.target_data = 14336;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 4096;
+    e.target_type = 0;
+    e.target_data = 24576;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_4_a,,,rw,0 10240 linear test 32768;part_1_b,,,rw,0 10240 linear test 43008;part_2_b,,,rw,0 10240 linear test 53248;part_3_b,,,rw,0 10240 linear test 63488;part_4_b,,,rw,0 10240 linear test 73728;part_3_a,,,rw,0 2048 linear test 22528;part_2_a,,,rw,0 2048 linear test 12288;part_1_a,,,rw,0 2048 linear test 2048;part_5_a,,,rw,0 8192 linear test 4096,8192 8192 linear test 14336,16384 4096 linear test 24576";
+    std::string output_list_tables = "part_4_a 0 10240 linear test 32768\npart_1_b 0 10240 linear test 43008\npart_2_b 0 10240 linear test 53248\npart_3_b 0 10240 linear test 63488\npart_4_b 0 10240 linear test 73728\npart_3_a 0 2048 linear test 22528\npart_2_a 0 2048 linear test 12288\npart_1_a 0 2048 linear test 2048\npart_5_a 0 8192 linear test 4096\\n8192 8192 linear test 14336\\n16384 4096 linear test 24576";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups; First has four partitions; Second group has four partitions; One partition has three extents
+TEST(ParseDynpartsTest, GroupAFourPartsGroupBFivePartsOneHasThreeExtents) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_8.img based on super_8.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 940 0 0 9 52  468 11 24  732 3 48  876 1 64  161 105 225 29 136 71 245 247 39 14 156 88 151 28 82 244 132 157 203 210 58 12 250 64 255 45 144 234 197 39 115 71 123 237 16 48 250 122 185 180 49 21 168 129 255 38 210 56 194 205 145 223 244 249 115 149 140 205 16 123 172 10 238 19 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  9 0 0 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 2 112 97 114 116 95 52 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 6 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 7 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 8 3 2 112 97 114 116 95 53 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  11 10240 0 2048 0  10240 0 12288 0  10240 0 22528 0  10240 0 32768 0  10240 0 73728 0  2048 0 63488 0  2048 0 53248 0  2048 0 43008 0  8192 0 45056 0  8192 0 55296 0  4096 0 65536 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 42991616 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 4);
+
+    strncpy(metadata.partitions[4].name, "part_4_b", 36);
+
+    strncpy(metadata.partitions[5].name, "part_3_b", 36);
+
+    strncpy(metadata.partitions[6].name, "part_2_b", 36);
+
+    strncpy(metadata.partitions[7].name, "part_1_b", 36);
+
+    LpMetadataPartition p;
+    p.attributes = 0;
+    p.first_extent_index = 8;
+    p.num_extents = 3;
+    p.group_index = 2;
+    strncpy(p.name, "part_5_b", 36);
+
+    metadata.partitions.push_back(p);
+
+    metadata.extents[0].num_sectors = 10240;
+    metadata.extents[0].target_data = 2048;
+
+    metadata.extents[1].num_sectors = 10240;
+    metadata.extents[1].target_data = 12288;
+
+    metadata.extents[2].num_sectors = 10240;
+    metadata.extents[2].target_data = 22528;
+
+    metadata.extents[3].num_sectors = 10240;
+    metadata.extents[3].target_data = 32768;
+
+    metadata.extents[4].num_sectors = 10240;
+    metadata.extents[4].target_data = 73728;
+
+    metadata.extents[5].num_sectors = 2048;
+    metadata.extents[5].target_data = 63488;
+
+    metadata.extents[6].num_sectors = 2048;
+    metadata.extents[6].target_data = 53248;
+
+    metadata.extents[7].num_sectors = 2048;
+    metadata.extents[7].target_data = 43008;
+
+    LpMetadataExtent e;
+    e.num_sectors = 8192;
+    e.target_type = 0;
+    e.target_data = 45056;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 8192;
+    e.target_type = 0;
+    e.target_data = 55296;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 4096;
+    e.target_type = 0;
+    e.target_data = 65536;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_1_a,,,rw,0 10240 linear test 2048;part_2_a,,,rw,0 10240 linear test 12288;part_3_a,,,rw,0 10240 linear test 22528;part_4_a,,,rw,0 10240 linear test 32768;part_4_b,,,rw,0 10240 linear test 73728;part_3_b,,,rw,0 2048 linear test 63488;part_2_b,,,rw,0 2048 linear test 53248;part_1_b,,,rw,0 2048 linear test 43008;part_5_b,,,rw,0 8192 linear test 45056,8192 8192 linear test 55296,16384 4096 linear test 65536";
+    std::string output_list_tables = "part_1_a 0 10240 linear test 2048\npart_2_a 0 10240 linear test 12288\npart_3_a 0 10240 linear test 22528\npart_4_a 0 10240 linear test 32768\npart_4_b 0 10240 linear test 73728\npart_3_b 0 2048 linear test 63488\npart_2_b 0 2048 linear test 53248\npart_1_b 0 2048 linear test 43008\npart_5_b 0 8192 linear test 45056\\n8192 8192 linear test 55296\\n16384 4096 linear test 65536";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
+
+// Two groups; First has five partitions; One partition has three extents; Second group has five partitions
+TEST(ParseDynpartsTest, GroupAFivePartsOneHasThreeExtentsGroupBFiveParts) {
+    LpMetadata metadata {};
+
+#ifdef METADATA_IO
+    // super_partitioned_9.img based on super_8.img
+    std::string metadata_str = "1634485351 52 65536 2 4096 78 49 207 100 39 84 66 244 14 37 199 114 161 141 31 204 216 177 41 18 50 229 147 246 94 82 47 199 172 7 223 3  1095520304 10 0 128 1016 0 0 10 52  520 12 24  808 3 48  952 1 64  21 53 9 199 89 140 129 245 91 161 143 41 255 149 151 157 155 203 217 8 132 22 238 245 22 56 224 117 118 185 42 167 194 50 247 62 63 4 83 80 202 200 136 62 41 177 101 250 248 251 229 180 136 119 221 159 217 173 146 147 254 84 38 251 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  10 0 0 1 1 112 97 114 116 95 52 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 1 1 2 112 97 114 116 95 52 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 2 1 1 112 97 114 116 95 51 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 3 1 1 112 97 114 116 95 50 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 4 1 1 112 97 114 116 95 49 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 5 3 1 112 97 114 116 95 53 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 8 1 2 112 97 114 116 95 51 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 9 1 2 112 97 114 116 95 50 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 10 1 2 112 97 114 116 95 49 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 11 1 2 112 97 114 116 95 53 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  12 10240 0 32768 0  10240 0 73728 0  2048 0 22528 0  2048 0 12288 0  2048 0 2048 0  8192 0 4096 0  8192 0 14336 0  4096 0 24576 0  2048 0 28672 0  2048 0 30720 0  2048 0 43008 0  20480 0 45056 0  3 0 0 100 101 102 97 117 108 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 97 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  0 20971520 103 114 111 117 112 95 98 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 2048 1048576 0 42991616 0 115 117 112 101 114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+    readMetadata(metadata, metadata_str);
+#else
+    addPartitions(metadata, 2, 5);
+
+    strncpy(metadata.partitions[0].name, "part_4_a", 36);
+
+    strncpy(metadata.partitions[1].name, "part_4_b", 36);
+    metadata.partitions[1].group_index = 2;
+
+    strncpy(metadata.partitions[3].name, "part_2_a", 36);
+
+    strncpy(metadata.partitions[4].name, "part_1_a", 36);
+
+    strncpy(metadata.partitions[5].name, "part_5_a", 36);
+    metadata.partitions[5].group_index = 1;
+    metadata.partitions[5].num_extents = 3;
+
+    strncpy(metadata.partitions[6].name, "part_3_b", 36);
+    metadata.partitions[6].first_extent_index = 8;
+
+    strncpy(metadata.partitions[7].name, "part_2_b", 36);
+    metadata.partitions[7].first_extent_index = 9;
+
+    strncpy(metadata.partitions[8].name, "part_1_b", 36);
+    metadata.partitions[8].first_extent_index = 10;
+
+    metadata.partitions[9].first_extent_index = 11;
+
+    metadata.extents[0].num_sectors = 10240;
+    metadata.extents[0].target_data = 32768;
+
+    metadata.extents[1].num_sectors = 10240;
+    metadata.extents[1].target_data = 73728;
+
+    metadata.extents[2].num_sectors = 2048;
+    metadata.extents[2].target_data = 22528;
+
+    metadata.extents[3].num_sectors = 2048;
+    metadata.extents[3].target_data = 12288;
+
+    metadata.extents[4].num_sectors = 2048;
+    metadata.extents[4].target_data = 2048;
+
+    metadata.extents[5].num_sectors = 8192;
+    metadata.extents[5].target_data = 4096;
+
+    metadata.extents[6].num_sectors = 8192;
+    metadata.extents[6].target_data = 14336;
+
+    metadata.extents[7].num_sectors = 4096;
+    metadata.extents[7].target_data = 24576;
+
+    metadata.extents[8].num_sectors = 2048;
+    metadata.extents[8].target_data = 28672;
+
+    metadata.extents[9].num_sectors = 2048;
+    metadata.extents[9].target_data = 30720;
+
+    LpMetadataExtent e;
+    e.num_sectors = 2048;
+    e.target_type = 0;
+    e.target_data = 43008;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+
+    e.num_sectors = 20480;
+    e.target_type = 0;
+    e.target_data = 45056;
+    e.target_source = 0;
+
+    metadata.extents.push_back(e);
+#endif
+
+    std::string output = "part_4_a,,,rw,0 10240 linear test 32768;part_4_b,,,rw,0 10240 linear test 73728;part_3_a,,,rw,0 2048 linear test 22528;part_2_a,,,rw,0 2048 linear test 12288;part_1_a,,,rw,0 2048 linear test 2048;part_5_a,,,rw,0 8192 linear test 4096,8192 8192 linear test 14336,16384 4096 linear test 24576;part_3_b,,,rw,0 2048 linear test 28672;part_2_b,,,rw,0 2048 linear test 30720;part_1_b,,,rw,0 2048 linear test 43008;part_5_b,,,rw,0 20480 linear test 45056";
+    std::string output_list_tables = "part_4_a 0 10240 linear test 32768\npart_4_b 0 10240 linear test 73728\npart_3_a 0 2048 linear test 22528\npart_2_a 0 2048 linear test 12288\npart_1_a 0 2048 linear test 2048\npart_5_a 0 8192 linear test 4096\\n8192 8192 linear test 14336\\n16384 4096 linear test 24576\npart_3_b 0 2048 linear test 28672\npart_2_b 0 2048 linear test 30720\npart_1_b 0 2048 linear test 43008\npart_5_b 0 20480 linear test 45056";
+    std::ostringstream messages;
+
+    EXPECT_EQ(go(metadata, false, "test", messages), output);
+    EXPECT_EQ(messages.str(), "");
+
+    EXPECT_EQ(go(metadata, true, "test", messages), output_list_tables);
+    EXPECT_EQ(messages.str(), "");
+}
\ No newline at end of file