Android Dynamic Partitions metadata: Perl parsing library

The metadata stored by Android at the beginning of a "super" partition, which is what it calls a disk partition that holds one or more Dynamic Partitions. Dynamic Partitions do more or less the same thing that LVM does on Linux, allowing Android to map ranges of non-contiguous extents to a single logical device. This metadata holds that mapping.

Application

Android

File extension

img

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.9

This page hosts a formal specification of Android Dynamic Partitions metadata using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Perl source code to parse Android Dynamic Partitions metadata

AndroidSuper.pm

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

use strict;
use warnings;
use IO::KaitaiStruct 0.009_000;
use Encode;

########################################################################
package AndroidSuper;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

}

sub root {
    my ($self) = @_;
    return $self->{root} if ($self->{root});
    my $_pos = $self->{_io}->pos();
    $self->{_io}->seek(4096);
    $self->{root} = AndroidSuper::Root->new($self->{_io}, $self, $self->{_root});
    $self->{_io}->seek($_pos);
    return $self->{root};
}

########################################################################
package AndroidSuper::Root;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{_raw_primary_geometry} = $self->{_io}->read_bytes(4096);
    my $io__raw_primary_geometry = IO::KaitaiStruct::Stream->new($self->{_raw_primary_geometry});
    $self->{primary_geometry} = AndroidSuper::Geometry->new($io__raw_primary_geometry, $self, $self->{_root});
    $self->{_raw_backup_geometry} = $self->{_io}->read_bytes(4096);
    my $io__raw_backup_geometry = IO::KaitaiStruct::Stream->new($self->{_raw_backup_geometry});
    $self->{backup_geometry} = AndroidSuper::Geometry->new($io__raw_backup_geometry, $self, $self->{_root});
    $self->{_raw_primary_metadata} = ();
    $self->{primary_metadata} = ();
    my $n_primary_metadata = $self->primary_geometry()->metadata_slot_count();
    for (my $i = 0; $i < $n_primary_metadata; $i++) {
        push @{$self->{_raw_primary_metadata}}, $self->{_io}->read_bytes($self->primary_geometry()->metadata_max_size());
        my $io__raw_primary_metadata = IO::KaitaiStruct::Stream->new($self->{_raw_primary_metadata}[$i]);
        push @{$self->{primary_metadata}}, AndroidSuper::Metadata->new($io__raw_primary_metadata, $self, $self->{_root});
    }
    $self->{_raw_backup_metadata} = ();
    $self->{backup_metadata} = ();
    my $n_backup_metadata = $self->primary_geometry()->metadata_slot_count();
    for (my $i = 0; $i < $n_backup_metadata; $i++) {
        push @{$self->{_raw_backup_metadata}}, $self->{_io}->read_bytes($self->primary_geometry()->metadata_max_size());
        my $io__raw_backup_metadata = IO::KaitaiStruct::Stream->new($self->{_raw_backup_metadata}[$i]);
        push @{$self->{backup_metadata}}, AndroidSuper::Metadata->new($io__raw_backup_metadata, $self, $self->{_root});
    }
}

sub primary_geometry {
    my ($self) = @_;
    return $self->{primary_geometry};
}

sub backup_geometry {
    my ($self) = @_;
    return $self->{backup_geometry};
}

sub primary_metadata {
    my ($self) = @_;
    return $self->{primary_metadata};
}

sub backup_metadata {
    my ($self) = @_;
    return $self->{backup_metadata};
}

sub _raw_primary_geometry {
    my ($self) = @_;
    return $self->{_raw_primary_geometry};
}

sub _raw_backup_geometry {
    my ($self) = @_;
    return $self->{_raw_backup_geometry};
}

sub _raw_primary_metadata {
    my ($self) = @_;
    return $self->{_raw_primary_metadata};
}

sub _raw_backup_metadata {
    my ($self) = @_;
    return $self->{_raw_backup_metadata};
}

########################################################################
package AndroidSuper::Geometry;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{magic} = $self->{_io}->read_bytes(4);
    $self->{struct_size} = $self->{_io}->read_u4le();
    $self->{checksum} = $self->{_io}->read_bytes(32);
    $self->{metadata_max_size} = $self->{_io}->read_u4le();
    $self->{metadata_slot_count} = $self->{_io}->read_u4le();
    $self->{logical_block_size} = $self->{_io}->read_u4le();
}

sub magic {
    my ($self) = @_;
    return $self->{magic};
}

sub struct_size {
    my ($self) = @_;
    return $self->{struct_size};
}

sub checksum {
    my ($self) = @_;
    return $self->{checksum};
}

sub metadata_max_size {
    my ($self) = @_;
    return $self->{metadata_max_size};
}

sub metadata_slot_count {
    my ($self) = @_;
    return $self->{metadata_slot_count};
}

sub logical_block_size {
    my ($self) = @_;
    return $self->{logical_block_size};
}

########################################################################
package AndroidSuper::Metadata;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

our $TABLE_KIND_PARTITIONS = 0;
our $TABLE_KIND_EXTENTS = 1;
our $TABLE_KIND_GROUPS = 2;
our $TABLE_KIND_BLOCK_DEVICES = 3;

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{magic} = $self->{_io}->read_bytes(4);
    $self->{major_version} = $self->{_io}->read_u2le();
    $self->{minor_version} = $self->{_io}->read_u2le();
    $self->{header_size} = $self->{_io}->read_u4le();
    $self->{header_checksum} = $self->{_io}->read_bytes(32);
    $self->{tables_size} = $self->{_io}->read_u4le();
    $self->{tables_checksum} = $self->{_io}->read_bytes(32);
    $self->{partitions} = AndroidSuper::Metadata::TableDescriptor->new($self->{_io}, $self, $self->{_root});
    $self->{extents} = AndroidSuper::Metadata::TableDescriptor->new($self->{_io}, $self, $self->{_root});
    $self->{groups} = AndroidSuper::Metadata::TableDescriptor->new($self->{_io}, $self, $self->{_root});
    $self->{block_devices} = AndroidSuper::Metadata::TableDescriptor->new($self->{_io}, $self, $self->{_root});
}

sub magic {
    my ($self) = @_;
    return $self->{magic};
}

sub major_version {
    my ($self) = @_;
    return $self->{major_version};
}

sub minor_version {
    my ($self) = @_;
    return $self->{minor_version};
}

sub header_size {
    my ($self) = @_;
    return $self->{header_size};
}

sub header_checksum {
    my ($self) = @_;
    return $self->{header_checksum};
}

sub tables_size {
    my ($self) = @_;
    return $self->{tables_size};
}

sub tables_checksum {
    my ($self) = @_;
    return $self->{tables_checksum};
}

sub partitions {
    my ($self) = @_;
    return $self->{partitions};
}

sub extents {
    my ($self) = @_;
    return $self->{extents};
}

sub groups {
    my ($self) = @_;
    return $self->{groups};
}

sub block_devices {
    my ($self) = @_;
    return $self->{block_devices};
}

########################################################################
package AndroidSuper::Metadata::BlockDevice;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{first_logical_sector} = $self->{_io}->read_u8le();
    $self->{alignment} = $self->{_io}->read_u4le();
    $self->{alignment_offset} = $self->{_io}->read_u4le();
    $self->{size} = $self->{_io}->read_u8le();
    $self->{partition_name} = Encode::decode("UTF-8", IO::KaitaiStruct::Stream::bytes_terminate($self->{_io}->read_bytes(36), 0, 0));
    $self->{flag_slot_suffixed} = $self->{_io}->read_bits_int_le(1);
    $self->{flags_reserved} = $self->{_io}->read_bits_int_le(31);
}

sub first_logical_sector {
    my ($self) = @_;
    return $self->{first_logical_sector};
}

sub alignment {
    my ($self) = @_;
    return $self->{alignment};
}

sub alignment_offset {
    my ($self) = @_;
    return $self->{alignment_offset};
}

sub size {
    my ($self) = @_;
    return $self->{size};
}

sub partition_name {
    my ($self) = @_;
    return $self->{partition_name};
}

sub flag_slot_suffixed {
    my ($self) = @_;
    return $self->{flag_slot_suffixed};
}

sub flags_reserved {
    my ($self) = @_;
    return $self->{flags_reserved};
}

########################################################################
package AndroidSuper::Metadata::Extent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

our $TARGET_TYPE_LINEAR = 0;
our $TARGET_TYPE_ZERO = 1;

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{num_sectors} = $self->{_io}->read_u8le();
    $self->{target_type} = $self->{_io}->read_u4le();
    $self->{target_data} = $self->{_io}->read_u8le();
    $self->{target_source} = $self->{_io}->read_u4le();
}

sub num_sectors {
    my ($self) = @_;
    return $self->{num_sectors};
}

sub target_type {
    my ($self) = @_;
    return $self->{target_type};
}

sub target_data {
    my ($self) = @_;
    return $self->{target_data};
}

sub target_source {
    my ($self) = @_;
    return $self->{target_source};
}

########################################################################
package AndroidSuper::Metadata::TableDescriptor;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{offset} = $self->{_io}->read_u4le();
    $self->{num_entries} = $self->{_io}->read_u4le();
    $self->{entry_size} = $self->{_io}->read_u4le();
}

sub table {
    my ($self) = @_;
    return $self->{table} if ($self->{table});
    my $_pos = $self->{_io}->pos();
    $self->{_io}->seek(($self->_parent()->header_size() + $self->offset()));
    $self->{_raw_table} = ();
    $self->{table} = ();
    my $n_table = $self->num_entries();
    for (my $i = 0; $i < $n_table; $i++) {
        my $_on = $self->kind();
        if ($_on == $AndroidSuper::Metadata::TABLE_KIND_PARTITIONS) {
            push @{$self->{_raw_table}}, $self->{_io}->read_bytes($self->entry_size());
            my $io__raw_table = IO::KaitaiStruct::Stream->new($self->{_raw_table}[$i]);
            push @{$self->{table}}, AndroidSuper::Metadata::Partition->new($io__raw_table, $self, $self->{_root});
        }
        elsif ($_on == $AndroidSuper::Metadata::TABLE_KIND_EXTENTS) {
            push @{$self->{_raw_table}}, $self->{_io}->read_bytes($self->entry_size());
            my $io__raw_table = IO::KaitaiStruct::Stream->new($self->{_raw_table}[$i]);
            push @{$self->{table}}, AndroidSuper::Metadata::Extent->new($io__raw_table, $self, $self->{_root});
        }
        elsif ($_on == $AndroidSuper::Metadata::TABLE_KIND_GROUPS) {
            push @{$self->{_raw_table}}, $self->{_io}->read_bytes($self->entry_size());
            my $io__raw_table = IO::KaitaiStruct::Stream->new($self->{_raw_table}[$i]);
            push @{$self->{table}}, AndroidSuper::Metadata::Group->new($io__raw_table, $self, $self->{_root});
        }
        elsif ($_on == $AndroidSuper::Metadata::TABLE_KIND_BLOCK_DEVICES) {
            push @{$self->{_raw_table}}, $self->{_io}->read_bytes($self->entry_size());
            my $io__raw_table = IO::KaitaiStruct::Stream->new($self->{_raw_table}[$i]);
            push @{$self->{table}}, AndroidSuper::Metadata::BlockDevice->new($io__raw_table, $self, $self->{_root});
        }
        else {
            push @{$self->{table}}, $self->{_io}->read_bytes($self->entry_size());
        }
    }
    $self->{_io}->seek($_pos);
    return $self->{table};
}

sub offset {
    my ($self) = @_;
    return $self->{offset};
}

sub num_entries {
    my ($self) = @_;
    return $self->{num_entries};
}

sub entry_size {
    my ($self) = @_;
    return $self->{entry_size};
}

sub kind {
    my ($self) = @_;
    return $self->{kind};
}

sub _raw_table {
    my ($self) = @_;
    return $self->{_raw_table};
}

########################################################################
package AndroidSuper::Metadata::Partition;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{name} = Encode::decode("UTF-8", IO::KaitaiStruct::Stream::bytes_terminate($self->{_io}->read_bytes(36), 0, 0));
    $self->{attr_readonly} = $self->{_io}->read_bits_int_le(1);
    $self->{attr_slot_suffixed} = $self->{_io}->read_bits_int_le(1);
    $self->{attr_updated} = $self->{_io}->read_bits_int_le(1);
    $self->{attr_disabled} = $self->{_io}->read_bits_int_le(1);
    $self->{attrs_reserved} = $self->{_io}->read_bits_int_le(28);
    $self->{_io}->align_to_byte();
    $self->{first_extent_index} = $self->{_io}->read_u4le();
    $self->{num_extents} = $self->{_io}->read_u4le();
    $self->{group_index} = $self->{_io}->read_u4le();
}

sub name {
    my ($self) = @_;
    return $self->{name};
}

sub attr_readonly {
    my ($self) = @_;
    return $self->{attr_readonly};
}

sub attr_slot_suffixed {
    my ($self) = @_;
    return $self->{attr_slot_suffixed};
}

sub attr_updated {
    my ($self) = @_;
    return $self->{attr_updated};
}

sub attr_disabled {
    my ($self) = @_;
    return $self->{attr_disabled};
}

sub attrs_reserved {
    my ($self) = @_;
    return $self->{attrs_reserved};
}

sub first_extent_index {
    my ($self) = @_;
    return $self->{first_extent_index};
}

sub num_extents {
    my ($self) = @_;
    return $self->{num_extents};
}

sub group_index {
    my ($self) = @_;
    return $self->{group_index};
}

########################################################################
package AndroidSuper::Metadata::Group;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{name} = Encode::decode("UTF-8", IO::KaitaiStruct::Stream::bytes_terminate($self->{_io}->read_bytes(36), 0, 0));
    $self->{flag_slot_suffixed} = $self->{_io}->read_bits_int_le(1);
    $self->{flags_reserved} = $self->{_io}->read_bits_int_le(31);
    $self->{_io}->align_to_byte();
    $self->{maximum_size} = $self->{_io}->read_u8le();
}

sub name {
    my ($self) = @_;
    return $self->{name};
}

sub flag_slot_suffixed {
    my ($self) = @_;
    return $self->{flag_slot_suffixed};
}

sub flags_reserved {
    my ($self) = @_;
    return $self->{flags_reserved};
}

sub maximum_size {
    my ($self) = @_;
    return $self->{maximum_size};
}

1;