Subversion Repositories DevTools

Rev

Rev 1107 | Blame | Compare with Previous | Last modification | View Log | RSS feed

########################################################################
# Copyright (C) 2008 ERG Limited, All rights reserved
#
# Module name   : CreateSdImage.pm
# Module type   : Makefile system
# Compiler(s)   : Perl
# Environment(s): jats
#
# Description   : Create a File System Image from a directory
#                 tree.
#                 Create the various file partitions and images
#                   0) Determine the partition sizes
#                   1) Create an empty image of the entire SD card
#                   2) Partition it up
#                   3) Determine the real size of each patition
#                   4) Create ext3 file system images
#                   5) Transfer the file system images into the SD image
#                   6) Install grub - if required
#
#......................................................................#

require 5.008_002;
use strict;
use warnings;

package CreateSdImage;

use JatsError;
use JatsSystem;
use FileUtils;
use File::Path;


# automatically export what we need into namespace of caller.
use Exporter();
our (@ISA, @EXPORT);
@ISA         = qw(Exporter);
@EXPORT      = qw(
                    CreateSdImage
                );

#
#   Data structure to describe the ouput partition(s)
#   Defined early so that it can be modified by the platform specific hook
#   Structure is a hash of hashed elements
#   Key: Partition number
#        Partition-0 is used to hold a complete (test) image
#
#   Value: A hash of other entries
#          These are
#          Key: name        - Name of the partition
#               root        - Partition mount point
#               size        - Size in Megabytes of the partition
#                             Set internally.
#                             'rest' is auto calculated, must be last
#               type        - Optional. If 'extended', then an extended
#                             partition will be created and all following
#                             partitions will be created in that extended
#                             partition
#               noprune     - Don't prune after partition
#                             Set internally.
#
our %partitions = (
        0 => { name=>"fullfs",  root=>"/"           },  # Just for Test

        1 => { name=>"rootfs",  root=>"/"           },
        2 => { name=>"logfs" ,  root=>"/var/log"    },
        3 => { name=>"afcfs",   root=>"/afc"        },
        4 => { name=>"varfs",   root=>"/var"        },

#       4 => { type=>"extended" },
    );

#-------------------------------------------------------------------------------
# Function        : CreateSdImage
#
# Description     : Create an ETX3 File System Image froma directory
#
# Inputs          : $outdir             - Target directory
#                   $image_name         - Root name of the output image
#                   $WORK               - Root of file system image
#                   $BUILD              - Scratch Work Area
#                   $diskGeometry       - Reference to disk Geometry hash
#                                         Keys are:
#                                           fullfs
#                                           size  
#                                           rootfs
#                                           logfs 
#                                           afcfs 
# Returns         :                         varfs 
#
sub CreateSdImage
{
    my ($outdir,$image_name,$WORK, $BUILD, $diskGeometry ) = @_;
    my $install_grub = ( -f "$WORK/boot/grub/grub" );

    #
    #   Ensure that the required utility programs can be found and that they
    #   are being consumed from packages and not from the users path
    #
    Error ( "Required utility program not found in package: e2fsimage" )
        unless ( LocateProgInPath ('e2fsimage' ) );

    Error ( "Required utility program not found in package: grub" )
        unless ( ! $install_grub || LocateProgInPath ('grub' ) );

    Error ("CreateSdImage:disk-blocks must be specified")
        unless ( $diskGeometry->{'size'}  );
    my $disk_blocks = $diskGeometry->{'size'};

    #
    #
    #   Magic: The size of the SD card
    #          This was determined by 'dd' ing an SD card and using the number of
    #          blocks that could be extracted.
    #
    my $disk_sectors = $disk_blocks * 2;
    $image_name .= '.raw';
    my $disk_image = "$BUILD/$image_name";
    my $pr_size = 20 * 2;                       # Sectors for a partition table

    #
    #   Determine partition start and end sectors
    #   Work in sectors
    #
    my  $pstart = $pr_size;
    my  $in_extended = 0;

    Message ("Calculate partition sizes");
    foreach my $partition ( sort keys %partitions )
    {
        my $pdata = $partitions{$partition};
        my $rest = 0;

        #
        #   Determine configured size
        #
        if ( $pdata->{name}  )
        {
            Error ("CreateSdImage: Partition size not specified: $pdata->{name} ")
                unless ( exists($diskGeometry->{$pdata->{name}}) );
            $pdata->{size} =  $diskGeometry->{$pdata->{name}} || 0;
        }

        unless ($partition )
        {
            $pdata->{size} *= 2;
            next;
        }

        #
        #   Validate the current start
        #
        if ( $pstart >= $disk_sectors )
        {
            my @data;
            foreach my $partition ( sort keys %partitions )
            {
                next unless ( $partition );
                my $pdata = $partitions{$partition};
                my $name = $pdata->{name};
                next unless ( $name );

                push @data, sprintf( "Partition-%d (%10s): Sector Size=~%5dM\n",
                            $partition,
                            $name,
                            ($diskGeometry->{$pdata->{name}} || 'Remainder') );
            }
            
            Error ("Invalid Disk Configuration. Partition Sizes exceed Disk size",
                   "Disk Size : $disk_blocks (~" . $disk_blocks / 1024 . ")M",
                    @data);
        }

        if (  exists $pdata->{type} && $pdata->{type} eq 'extended' )
        {
            $pdata->{start} = $pstart;
            $pdata->{end} =   $disk_sectors;
            $pdata->{size} = ($disk_sectors - $pstart);
            $pdata->{id} = 5;
            $in_extended = 1;
        }
        else
        {
            Error ("Partition Table setup. The 'rest' keyword must only be used on the last partition") if ( $rest );
            $pdata->{type} = 'Linux';
            $pdata->{id} = 83;
            $pstart += $pr_size if ( $in_extended );

            if ( $pdata->{size} eq 'rest' )
            {
                Error ("Partition Table setup. The 'rest' keyword can only be used once") if ( $rest );
                $pdata->{size} = ($disk_sectors - $pstart) / 2048;
                $rest = 1;
            }
        
            $pdata->{start} = $pstart;
            $pdata->{end} =   $pstart + ( $pdata->{size} * 1024 * 2);
            $pdata->{size} =  $pdata->{end} - $pdata->{start};
            $pstart = $pdata->{end};
        }
    }

    #
    #   Display calculated partition sizes
    #
    foreach my $partition ( sort keys %partitions )
    {
        next unless ( $partition );
        my $pdata = $partitions{$partition};
        my $name = $pdata->{name} || "";

        printf "Partition-%d (%10s): Sector Start=%10d, End=%10d, Sector Size=%10d(~%5dM), Id=%s\n",
                    $partition,
                    $name,
                    $pdata->{start},
                    $pdata->{end},
                    $pdata->{size},
                    $pdata->{size}/2/1024,
                    $pdata->{id};
    }

    Message ("Creating Empty SD Image");
    System ('dd', 'if=/dev/zero', "of=$disk_image", 'bs=1024', "count=$disk_blocks" );

    Message ("Creating Partition Table");
    open (SFDISK, "| /sbin/sfdisk  -f -L $disk_image" ) || Error ("Cannot invoke sfdisk");
    print SFDISK "unit: sectors\n\n";
    foreach my $partition ( sort keys %partitions )
    {
        next unless ( $partition );
        my $pdata = $partitions{$partition};
        printf SFDISK "Partition-%d: start=%10d, size=%10d, Id=%s\n",
                    $partition,
                    $pdata->{start},
                    $pdata->{size},
                    $pdata->{id};
    }
    close (SFDISK);

    ################################################################################
    #
    #   Split the complete file system image into required partitions
    #   Once the partition has been created it will be removed from the image
    #   What is left is the root file system - the final partition
    #
    #   Note: Size values are taken after the SD card has been partitioned
    #         The partioning process wants to place partitions on cylinder
    #         boundaries. This is why the number are not nice powers of two or 10
    #
    #         Partition logic:
    #           P1 root        - 30Meg
    #           P2 log         - 20Meg              Mounted as /var/log)
    #           P3 afc         - 150Meg
    #           P4 var         - Rest (~290 Meg)
    #
    #
    #   Note: Order is important, as dir tree will be pruned
    #         Be smart and determine order to create_fs
    #
    foreach my $partition ( CalcPartitionOrder() )
    {
        create_fs ($WORK, $BUILD, $partition);
    }

    #
    #   Insert the images into the SD image
    #
    foreach my $partition ( sort keys %partitions )
    {
        next unless ( $partition );
        my $pdata = $partitions{$partition};
        next unless ( defined $pdata->{fsname} );

        Message ("Insert filesystem $pdata->{name} into SD Image");
        my $cmd = "dd if=$pdata->{fsname} of=$disk_image bs=512 conv=notrunc seek=$pdata->{start} count=$pdata->{size}";
        System ($cmd);
    }

    ################################################################################
    #   
    #   Install grub into the MBR of the file system [ optional]
    #   This is only required for an x86 target and will detected by the
    #   presence of a key grub file
    #   The grub files will have been copied into the filesystem image earlier
    #
    if ( $install_grub )
    {
        Message ("Installing Grub into the MBR");

        #
        #   Need to run a copy of grab (built for the build machine) on the image
        #
        #   Need a command file to feed into grub
        #   Prevent grub from scanning bios
        #
        my $map_file = "$BUILD/device_map";
        open (GRUB, ">$map_file" ) || Error ("Cannot create grub device map file: $map_file");
        print GRUB "(hd0) $disk_image\n";
        close (GRUB);

        #
        #   Run grub and pipe in commands
        #
        open (GRUB, "| grub  --device-map=$map_file --batch" ) || Error ("Cannot invoke GRUB");
        print GRUB "device (hd0) $disk_image\n";
        print GRUB "root (hd0,0)\n";
        print GRUB "setup (hd0)\n";
        close (GRUB);

        #
        #   Cleanup temp files
        #
        unlink $map_file;
    }

    ################################################################################
    #
    #   Package up the file system images
    #   Transfer the required build artifacts directly into the package
    #
    #   This images are large. They are stored in a gzip form to compress them
    #
    #   Output to disk as:
    #       gunzip -c rootfs.ext3.gz | dd  of=/dev/sdb1 bs=10K
    #
    mkpath( $outdir, 0, 0775);
    Message ("Package up the (compressed) filesystem images");
    my %package_list;

    #
    #   Complete image
    #
    my $ofile = "$outdir/${image_name}.gz";
    $package_list{$ofile}{type} = 'image';
    System ("gzip -c $disk_image > $ofile");

#    #
#    #   Create test images of the internal partitions
#    #
#    mkpath( "$outdir/test", 0, 0775);
#    foreach my $partition ( sort keys %partitions )
#    {
#        my $pdata = $partitions{$partition};
#        next unless ( defined $pdata->{fsname} );
#
#        my $file = $pdata->{fsname} ;
#        my $ofile = StripDir($file) ;
#        $ofile = "$outdir/test/${ofile}.gz";
#        $package_list{$ofile}{type} = 'ext3';
#        $package_list{$ofile}{partition} = $partition;
#        System ("gzip -c $file > $ofile");
#    }
#
    #
    #   Useful information for testing
    #
    Message ("Useful load commands for testing");
    foreach my $entry ( sort keys %package_list )
    {
        next unless ( defined $package_list{$entry}{partition} );

        my $ifname = AbsPath( $entry );
        my $ofname = "/dev/sdb" . $package_list{$entry}{partition} ;

        my $cmd = "gunzip -c $ifname | dd bs=1M of=$ofname";
        Message $cmd;
    }

    foreach my $entry ( sort keys %package_list )
    {
        next if ( defined $package_list{$entry}{partition} );

        my $ifname = AbsPath( $entry );
        my $ofname = "/dev/sdb";

        my $cmd = "gunzip -c $ifname | dd bs=1M of=$ofname";
        Message $cmd;
    }
}

#-------------------------------------------------------------------------------
# Function        : create_fs
#
# Description     : Create the ext3 image of a given partition
#
# Inputs          : $WORK               - Root of data
#                   $BUILD              - Scratch Area
#                   $FS_PART            - Partition number
#
# Returns         : 
#
sub create_fs
{
    my ($WORK, $BUILD, $FS_PART) = @_;
    my $pdata = $partitions{$FS_PART} || Error ("Unknown partition number: $FS_PART");
    my $FS_IMAGE = $pdata->{name};
    my $FS_BASE =  $pdata->{root};
    my $FS_SIZE = $pdata->{size} / 2;

    #
    #   The 'full' image may not be required
    #   It will have a zero size
    #
    return unless ( $FS_SIZE );

    Message "Creating EXT3 file system: ${FS_IMAGE}, Partition: ${FS_PART}, Size: ${FS_SIZE} blocks";

    $FS_IMAGE = "${BUILD}/${FS_IMAGE}.ext3";
    $pdata->{fsname} = $FS_IMAGE;
    unlink ${FS_IMAGE};

    my $cmd = "e2fsimage -f ${FS_IMAGE} -d ${WORK}${FS_BASE} -u 0 -g 0 -s ${FS_SIZE}";
    $cmd .= " -v " if ( IsVerbose(1) );
    Verbose $cmd;
    open (E2FSIMAGE, "$cmd |" ) || Error ("Can't invoke e2fsimage");
    while ( <E2FSIMAGE> )
    {
        chomp;
        Verbose ("          $_");
    }
    close (E2FSIMAGE);

    $cmd = "/sbin/tune2fs -j -c 10 -i 5d ${FS_IMAGE}";
    Verbose $cmd;
    open (E2FSIMAGE, "$cmd |" ) || Error ("Can't invoke tunefs");
    while ( <E2FSIMAGE> )
    {
        chomp;
        Verbose ("          $_");
    }
    close (E2FSIMAGE);

    #
    #   Clean the subtree that has been encapsulated in the partition
    #   Leave the mountpoint
    #
    unless ( $pdata->{noprune} )
    {
        Verbose ("Cleaning partition data - leave mount point", $pdata->{root});
        foreach my $file ( glob("${WORK}/${FS_BASE}/*") )
        {
            if ( -d $file )
            {
                rmtree( $file );
            }
            else
            {
                unlink ($file);
            }
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : CalcPartitionOrder
#
# Description     :  Determine the order in which we need to create the partitions
#                    Simple: if 'A' is a subset of 'B' then 'A' must be done first
#
# Inputs          :
#
# Returns         : 
#
sub CalcPartitionOrder
{
    my %pdata;
    my @porder;

    #
    #   Examine all partitions
    #   Partition-0: process first, don't prune
    #   Ignore extended partions. The only affect the partition  table
    #
    foreach my $partition ( sort keys %partitions )
    {
        if ( $partition == 0 )
        {
            push @porder, $partition;
            $partitions{$partition}{noprune} = 1;
            next;
        }
        next if ( ($partitions{$partition}{type} || '') eq 'extended'  );
        $pdata{$partitions{$partition}{root}} = $partition;
    }

    #
    #   Now we have the names of the partions
    #   Reverse Order
    #
    foreach my $name ( reverse sort keys %pdata )
    {
        push @porder, $pdata{$name};
    }

    #
    #   Mark the last partition as non-prune
    #
    my $last = $porder[$#porder];
    $partitions{$last}{noprune} = 1;

    #
    #   Return the order in which partitions MUST be pruned
    #
    return @porder;
}