######################################################################## # 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 ( ) { 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 ( ) { 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; }