Subversion Repositories DevTools

Rev

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

########################################################################
# Copyright (C) 2008 ERG Limited, All rights reserved
#
# Module name   : Proc_day0fs_script.pl
# Module type   : Makefile system
# Compiler(s)   : Perl
# Environment(s): jats
#
# Description   :
#
# Usage:
#
#......................................................................#

require 5.008_002;

use strict;
use warnings;

use Pod::Usage;
use Getopt::Long;
use File::Path;
use File::Copy;

use FindBin;                                    # Determine the current directory
use lib "$FindBin::Bin";                        # Allow Modules in current directory

use JatsError;
use JatsSystem;
use FileUtils;
use JatsCopy qw(SetCopyDirDefaults DeleteDir CopyDir CreateDir);
use CreateSdImage qw( CreateSdImage );

#
#
#   Global Variables
#
my $VERSION = 1.0;
our $AUTOLOAD;
my $BUILD = 'build';                        # Build subdir
my $WORK = "${BUILD}/image";

my $source_specified = 0;                   # Ensure that source is specified
my $output_image = 0;                       # Ensure output is specified
my $output_sd = 0;

#
#   Command line options
#
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
my $opt_vargs;                              # Verbose arg
my $opt_help = 0;
my $opt_manual = 0;
my $opt_clean = 0;
my $opt_platform;
my $opt_type;
my $opt_buildname;
my $opt_packagedir;
my $opt_interfacedir;
my $opt_compilerpath;
my $opt_product;
my $opt_packagebindir;
our $opt_buildroot;
my $opt_target;
my $opt_version;
my $opt_userscript;

#-------------------------------------------------------------------------------
# Function        : Mainline Entry Point
#
# Description     :
#
# Inputs          :
#
my $result = GetOptions (
                "help+"             => \$opt_help,
                "manual"            => \$opt_manual,
                "verbose:s"         => \$opt_vargs,
                "clean"             => \$opt_clean,
                "Platform=s"        => \$opt_platform,
                "Type=s"            => \$opt_type,
                "BuildName=s"       => \$opt_buildname,
                "PackageDir=s"      => \$opt_packagedir,
                "InterfaceDir=s"    => \$opt_interfacedir,
                "CompilerPath=s"    => \$opt_compilerpath,
                "Product=s"         => \$opt_product,
                "PackageBinDir=s"   => \$opt_packagebindir,
                "BuildRoot=s"       => \$opt_buildroot,
                "Target=s"          => \$opt_target,
                "BuildVersion=s"    => \$opt_version,
                "UserScript=s"      => \$opt_userscript,

    );

#
#   Process help and manual options
#
pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result);
pod2usage(-verbose => 1)  if ($opt_help == 2 );
pod2usage(-verbose => 2)  if ($opt_manual || ($opt_help > 2));

#
#   Configure the error reporting process now that we have the user options
#
$opt_verbose++ unless ( $opt_vargs eq '@' );
ErrorConfig( 'name'    => 'Proc_Day0fs',
             'verbose' => $opt_verbose );

#
#   Configure the System command to fail on any error
#
SystemConfig ( ExitOnError => 1 );
SetCopyDirDefaults ( 'EmptyDirs' => 1 ,Log => 0 );

#
#   Init the FileSystem Uiltity interface
#
InitFileUtils();

#
#   Ensure that we have all required options
#
Error ("CompilePath not set")       unless ( $opt_compilerpath );
Error ("Platform not set")          unless ( $opt_platform );
Error ("Type not set")              unless ( $opt_type );
Error ("BuildName not set")         unless ( $opt_buildname );
Error ("PackageBinDir not set")     unless ( $opt_packagebindir );
Error ("InterfaceDir not set")      unless ( $opt_interfacedir );
Error ("CompilePath not set")       unless ( $opt_compilerpath );
Error ("Product not set")           unless ( $opt_product );
Error ("BuildRoot not set")         unless ( $opt_buildroot );
Error ("Target not set")            unless ( $opt_target );
Error ("User Script not set")       unless ( $opt_userscript );

#
#   Calculate a few paths
#
my $TOOLSUITE = $opt_compilerpath;
$TOOLSUITE =~ s~^.*/~~;
$TOOLSUITE =~ s~-gcc$~~;

my $CROSSLIBS = $opt_compilerpath;
$CROSSLIBS =~ s~/bin/.*$~~;
$CROSSLIBS .= "/${TOOLSUITE}/lib";

my $CROSSEXE = $opt_compilerpath;
$CROSSEXE =~ s~/bin/.*$~~;
$CROSSEXE .= "/${TOOLSUITE}";


my $CROSS_COMPILE = $opt_compilerpath;
$CROSS_COMPILE =~ s~gcc$~~;

my $CROSS_OBJCOPY = ${CROSS_COMPILE} . "objcopy";

#
#   Display variables used
#
Message    "======================================================================";
Message    "Build day0 file system";
Message    "           Package: ${opt_buildname}";
Message    "           Version: ${opt_version}";
Message    "      Building for: ${opt_platform}, $opt_target";
Message    "           Product: ${opt_product}";
Message    "              Type: ${opt_type}";
Message    "            Script: ${opt_userscript}";
Verbose    "           Verbose: ${opt_verbose}";
Message    "         BuildRoot: ${opt_buildroot}";
Verbose    "         BuildRoot: ${opt_buildroot}";
Verbose    "      InterfaceDir: ${opt_interfacedir}";
Verbose    "     CROSS_COMPILE: ${CROSS_COMPILE}";
Verbose    "         CROSSLIBS: ${CROSSLIBS}";
Verbose    "     CROSS_OBJCOPY: ${CROSS_OBJCOPY}";
Message    "======================================================================";

#
#   Perform Clean up
#   Invoked during "make clean" or "make clobber"
#
if ( $opt_clean )
{
    Message ("Remove build directory: $BUILD");
    rmtree( $BUILD );
    exit;
}

#
#   Have setup the basics
#   Invoke the user script to drive the remainder of the process
#       The script should only contain known directives
#       The directives will build up data structures that will be processed
#       after the script has been read
#
#
Message ("Process User Script: $opt_userscript");
Error ("Board Specfic file Not found: $opt_userscript") unless ( -f $opt_userscript );
require $opt_userscript;

#
#   The process of requiring the user script
#   will have done all the required work
#   Perform minor sanity testing
#
#
#   All done
#
Error ( "No package output specified" )
    unless ( $output_image || $output_sd );
exit 0;


################################################################################
################################################################################
#
#   The following functions are intended to be used to manipulate files within
#   the output image.
#
#   These may be used by the board specfic functions.
#
################################################################################

#-------------------------------------------------------------------------------
# Function        : Day0Source
#
# Description     : Determine the source of the Day0 File System
#                   Use of this directive is mandatory
#
#
# Inputs          : Options
#                       --Dir=name              - Source dir
#                       --Image=name            - Source image
#
# Returns         : Nothing
#
sub Day0Source
{
    my ($opt) = @_;
    #
    #   Can only use this directive ONCE
    #
    Error ("Multiple Day0Source directives not allowed")
        if ( $source_specified++ );

    #
    #   Start with a clean slate
    #
    DeleteDir ( $WORK );

    #
    #   Process options
    #
    if ( $opt =~ /^--Dir=(.+)/ ) {
        #
        #   User has specified a directory
        #   Copy the entire directory to the work area
        #
        Message ("Copy in Day0 source directory: $WORK");
        CopyDir ( $1, $WORK, 'DeleteFirst' => 1 );

    } elsif ( $opt =~ /^--Image=(.+)/ ) {

        #
        #   User has specified an Image
        #   Locate the image and extract it into the work area
        #
        my $name = $1;
        Message ("Extract File System Image: $name");

        #
        #   File will come from a Package
        #   This is where the tool places output files
        #
        my $fname = Internal::LocateFile ('bin', $name . '.tgz' );
        Error ("Cannot locate Day0 source image: $name")
            unless $fname;

        #
        #   Create a Tar command
        #   Assumes the presence of a GNU style tar
        #
        CreateDir ( $WORK );
        System ( 'tar',
                 '--extract',
                 '--file',  $fname,
                 '--gzip',
                 IsVerbose(1) ? ( '--verbose' ) : (),
                 '--directory', $WORK,
                 );

    } else {
        Error ("Unknown Day0Source option: $opt");
    }
}


#-------------------------------------------------------------------------------
# Function        : Day0Operation
#
# Description     : Perform one of the special Day0 Operations
#
# Inputs          : $subfunction            - Sub function to perform
#                   @opts                   - Sub function options
#
# Returns         : Nothing
#
sub Day0Operation
{
    Error ("Day0Source must be specified first")
        unless ( $source_specified );

    #
    #   Perform the Day0Operation
    #   These are specialised operations and normally only performed on the
    #   the basic skelton.
    #
    #   Dispatch to the required function
    #
    my $opt = shift (@_);
    if ( my $fref = UNIVERSAL::can ( 'Day0Ops', lc($opt) ) )
    {
        &$fref( @_ );
    }
    else
    {
        Error ("Unknown Day0Operation option: $opt @_");
    }
}

#-------------------------------------------------------------------------------
# Function        : Day0BuildImage
#
# Description     : Package up the created image as a tar-zip file
#                   This allows the Image to be processed again later
#
# Inputs          : Options
#                       --name=Name             - Output Name
#
# Returns         : 
#
sub Day0BuildImage
{
    my $name = 'Day0Image';
    Message ("Create File System Image; $name");

    #
    #   Indicate that we have created some output
    #
    Error ("Day0Source must be specified first")
        unless ( $source_specified );
    $output_image++;

    #
    #   Process user options
    #
    foreach  ( @_ )
    {
        if ( m~^--Name=(.+)~i ) {
            $name = $1;
        } else {
            Error ("Day0BuildImage: Unknown option: $_");
        }
    }

    #
    #   Place the file in the BIN directory
    #   Have no reall good reason
    #
    $name = $opt_packagebindir . '/' . $name . '.tgz';
    CreateDir ( $opt_packagebindir);

    #
    #   Create a Tar command
    #   Assumes the presence of a GNU style tar
    #
    System ( 'tar',
             '--create',
             '--file',  $name,
             '--gzip',
             IsVerbose(1) ? ( '--verbose' ) : (),
             '--directory', $WORK,
             '.' );
}

#-------------------------------------------------------------------------------
# Function        : Day0BuildSdImage
#
# Description     : Package up the created image and create an SD memory image
#                   This is destructive and can only be done one
#
# Inputs          : Options
#                       --disk_blocks           - Size the output disk
#                       --full_image            - Size of Full Image
#                       --debug
#
# Returns         : 
#
sub Day0BuildSdImage
{
    my $debug;
    my %diskGeometry = (
            fullfs  => 0,                   # Test Image Size
            size    => 501248,              # Block Size of Entire Disk
            rootfs  => 30,                  # Root Size (Megabytes)
            logfs   => 20,                  # Log
            afcfs   => 150,                 # AFC
            varfs   => 'rest'               # Var - has the rest
            );


    #
    #   Ensure that some work is being done
    #
    Error ("Day0Source must be specified first")
        unless ( $source_specified );

    Error ("Cannot create multiple SD images")
        if ( $output_sd++ );

    #
    #   Process user options
    #
    foreach  ( @_ )
    {
        if ( m~^--disk-blocks=(\d+)$~i ) {
            $diskGeometry{'size'} = $1;

        } elsif ( m~^--full_image=(\d+)$~i ) {
            $diskGeometry{'fullfs'} = $1;

        } elsif ( m~^--rootfs=(\d+)$~i ) {
            $diskGeometry{'rootfs'} = $1;

        } elsif ( m~^--logfs=(\d+)$~i ) {
            $diskGeometry{'logfs'} = $1;

        } elsif ( m~^--afcfs=(\d+)$~i ) {
            $diskGeometry{'afcfs'} = $1;
            
        } elsif ( m~^--varfs=~i ) {
            Error ('Var File System cannot be configured');

        } elsif ( m~^--debug~i ) {
            $debug = 1;
        } else {
            Error ("Day0BuildSdImage: Unknown option: $_");
        }
    }

    #
    #   Perform specific processing on the image
    #

    #
    #   Scan the skeleton and process .LINKS files
    #   File links do not store well in version control systems
    #   NOTE: .LINKS are a local invention and not a part of e2fsimage like
    #         .DEVICES on which they are modelled.
    #
    Message ("Locate LINKFILES in $WORK");
    foreach my $linkfile ( Internal::FindFiles( $WORK, ".LINKS" ))
    {
        my $BASEDIR = StripFileExt( $linkfile );
        $BASEDIR =~ s~^$WORK/~~;
        Message "Expand links: $BASEDIR";
        open (LF, "<", $linkfile ) || Error ("Cannot open link file: $linkfile" );
        while ( <LF> )
        {
            chomp;
            next if ( m~^#~ );
            next unless ( $_ );
            my ($link, $file) = split;

            Internal::MakeSymLink($file ,"$BASEDIR/$link", '--NoDotDot' );
        }
        close (LF);
        unlink $linkfile;
    }
    

    #   Update the Shared Library Cache
    #
    Message ("Create Shared Library Cache");
    System ('/sbin/ldconfig', '-r', $WORK );

    #
    #   Make every thing executable
    #       This is good for directories
    #       This is good for all files. Shouldn't be an issue to files in /etc
    #
    Message ("Mark files as executable");
    foreach my $dir ( glob ("$WORK/*") )
    {
        next if ( $dir =~ m~/etc$~ );
        next if ( $dir =~ m~/linuxrc$~ );
        System ('chmod', '-R', 'a+rx', $dir );
    }

    foreach my $dir ( glob ("$WORK/etc/*") )
    {
        next unless ( -d $dir );
        System ('chmod', '-R', 'a+rx', $dir );
    }

    #
    #   Special considertaion
    #       /etc/busybox.conf   - Accessible by root
    #       /bin/busybox        - setuid
    #
    System ('chmod', '600',  "$WORK/etc/busybox.conf" );
    System ('chmod', '4755', "$WORK/bin/busybox" );

    #
    #   Stop here if debugging
    #   Don't create the actual image as the process destroys the image
    #
    Error ("Day0BuildSdImage: Debug Image. SD Image not built")
        if ( $debug );

    #
    #   Invoke SD build function
    #   Held in another package to make this one readable
    #
    my $name = "${opt_buildname}-${opt_version}-${opt_target}";
    CreateSdImage( $opt_packagebindir, $name, $WORK, $BUILD, \%diskGeometry );
}

#-------------------------------------------------------------------------------
# Function        : AddInitScript
#
# Description     : Add an Init Script to the target
#                   Optionally create start and stop links
#
#                   Existing links will always be delete
#
# Inputs          : $script     - Name of the init script
#                   $start      - Start Number
#                   $stop       - Stop Number
#                   Options:
#                       --NoCopy    - Don't copy the script, just add links
#
# Returns         : 
#
sub AddInitScript
{
    my ( $script, $start, $stop, @opts ) = @_;

    Error ("Day0Source must be specified first")
        unless ( $source_specified );
    
    Message ("AddInitScript: $script, $start, $stop");

    my $tdir = "/etc/init.d/init.d";
    my $base = StripDir($script);

    unless ( grep '^--NoCopy', @opts )
    {
        ::CopyFile( $script, $tdir );
    }

    #
    #   Delete any existing links
    #
    foreach my $file ( glob("$WORK/etc/init.d/*$base") )
    {
        next unless ( $file =~ m~/[KS][\d]+${base}~ );
        unlink $file;
    }

    my $link;
    if ( $start )
    {
        $link = sprintf ("/etc/init.d/S%2.2d%s", $start, $base );
        Internal::MakeSymLink( "$tdir/$base", $link);
    }

    if ( $stop )
    {
        $link = sprintf ("/etc/init.d/K%2.2d%s", $stop, $base );
        Internal::MakeSymLink( "$tdir/$base", $link);
    }
}

#-------------------------------------------------------------------------------
# Function        : EchoFile
#
# Description     : Echo simple text to a file
#
# Inputs          : $file   - Within the output workspace
#                   $text
#
# Returns         : 
#
sub EchoFile
{
    my ($file, $text) = @_;
    Error ("Day0Source must be specified first")
        unless ( $source_specified );

    $file = $WORK . '/' . $file;
    $file =~ s~//~/~;

    unlink $file;
    open (DT, ">", $file ) || Error ("Cannot create $file");
    print DT  $text || Error ("Cannot print to $file");
    close DT;
}

#-------------------------------------------------------------------------------
# Function        : CopyPkgDir
#
# Description     : Copy a directory to a target dir
#                   Source directory is within a 'package'
#                   Does not delete the target directory, but will add files
#
#                   Currently does not support
#                       Recursion
#                       Flattening
#
#
# Inputs          : $src_dir    - Within a package
#                   $dst_dir    - Within the output workspace
#
#                   Options:
#                       --FilterIn=Filter           Simple filter
#                       --FilterOut=Filter          Simple filter
#
# Returns         :
#
sub CopyPkgDir
{
    my @filter_out;
    my @filter_in;
    my @args;

    Error ("Day0Source must be specified first")
        unless ( $source_specified );

    #
    #   Process options
    #
    foreach  ( @_ )
    {
        if ( m/^--FilterIn=(.+)/ ) {
            push @filter_in, $1;

        } elsif ( m/^--FilterOut=(.+)/ ) {
            push @filter_out, $1;

        } elsif ( m/^-/ ) {
            Error("CopyPkgDir: Unknown option: $_");

        } else {
            push @args, $_;
        }
    }
    my ($src_dir, $dst_dir) = @args;
    #
    #   Calculate the destination path
    #
    $dst_dir = $WORK . '/' . $dst_dir;
    $dst_dir =~ s~//~/~;

    #
    #   Validate source dir
    #
    my $full_src_dir = "$opt_interfacedir/$src_dir";
    Error("CopyPkgDir: Source dir not found: $src_dir") unless ( -d $full_src_dir );

    #
    #   Copy as required
    #
    CopyDir ( $full_src_dir, $dst_dir,
                    'Flatten' => 1,
                    'NoSubDirs' => 1,
                    'Ignore' =>  \@filter_out,
                    'Match' => \@filter_in,
                    );
}

#-------------------------------------------------------------------------------
# Function        : CopyPackage
#
# Description     : Copy in files from a package located in the interface
#                   directory.
#
#                   The source directory may be:
#                       pkg/<platform>
#                       pkg/<product>
#                       pkg/<target>
#
# Inputs          : $section    - Section to search
#                   $file       - Source Path within a package
#                   @llist      - list of softlinks
#                                 Abs or Relative to destination dir
#
#                   Embedded Options:
#                               --Dest=Dir (Default is taken from $file)
#                               --LinkFile (Append to .LINKS file)
#                               --Rename=Name
#
# Returns         :  Path to the target file
#
sub CopyPackage
{
    my $dest;
    my $isa_linkfile;
    my @args;
    my $dfile;
    my @options;

    Error ("Day0Source must be specified first")
        unless ( $source_specified );
    
    #
    #   Process and Remove options
    #
    foreach  ( @_ )
    {
        if ( m/^--Dest=(.*)/ ) {
            $dest = $1 . '/xxx';

        } elsif ( m/^--LinkFile/ ) {
            $isa_linkfile = 1;

        } elsif ( m/^--Rename=(.+)/ ) {
            push @options, $_;

        } elsif ( m/^--/ ) {
            Error ("CopyPackage: Unknown option: $_");

        } else {
            push @args, $_;

        }
    }

    my ($section, $file, @llist) = @args;

    #
    #   Default destination is the same as the source
    #
    $dest = $file unless ( $dest );
    my $dest_dir = StripFileExt($dest);

    my $tfile = Internal::LocateFile($section, $file);
    Error ("CopyPackage: File not found: $file") unless $tfile;

    #
    #   LinkFiles are special
    #   They get concatenated to any existing Link File
    #
    if ( $isa_linkfile )
    {
        CatFile ( $tfile, "$dest_dir/.LINKS" );
    }
    else
    {
        $dfile = CopyFile ($tfile, $dest_dir, @options );
        foreach my $lname ( @llist )
        {
            $lname = $dest_dir . '/' . $lname unless ( $lname =~ m ~^/~ );
            my $dest_file = $dest_dir . '/' . StripDir($dfile);
            Internal::MakeSymLink($dest_file ,$lname);
        }
    }

    return $dfile;
}

#-------------------------------------------------------------------------------
# Function        : CatFile
#
# Description     : Copy a file to the end of a file
#
# Inputs          : $src
#                   $dst    - Within the output workspace
#
# Returns         :
#
sub CatFile
{
    my ($src, $dst) = @_;
    $dst = $WORK . '/' . $dst;
    $dst =~ s~//~/~;
    Verbose ("CatFile: $src, $dst");

    Error ("Day0Source must be specified first")
        unless ( $source_specified );

    open (SF, '<', $src)  || Error ("CatFile: Cannot open $src");
    open (DF, '>>', $dst) || Error ("CatFile: Cannot create:$dst");
    while ( <SF> )
    {
        print DF $_;
    }
    close (SF);
    close (DF);
}


#-------------------------------------------------------------------------------
# Function        : CopyFile
#
# Description     : Copy a file to a target dir
#
# Inputs          : $src
#                   $dst_dir    - Within the output workspace
#                   Options     - Optional flags
#                                 --Symlink
#                                   Copies symlink, not symlink target
#                                 --Rename=xxx
#                                   Renames file
#
# Returns         : Full path to destination file
#
sub CopyFile
{
    my ($src, $dst_dir, @opts ) = @_;
    my $tfile = StripDir($src);
    my %opts;

    Error ("Day0Source must be specified first")
        unless ( $source_specified );
    
    #
    #   Extract options
    #
    foreach  ( @opts )
    {
        if ( m/^--Symlink/ ) {
            $opts{DuplicateLinks} = 1;

        } elsif ( m/^--Rename=(.+)/ ) {
            $tfile = $1;

        } else {
            Error("CopyFile: Unknown option: $_");
        }
    }

    #
    #   Calculate the destination path
    #
    $dst_dir = $WORK . '/' . $dst_dir;
    $dst_dir =~ s~//~/~;

    #
    #   Determine the full name of the target
    #
    my $dst_file = "$dst_dir/$tfile";

    Verbose ("CopyFile: $src, $dst_dir");
    return JatsCopy::CopyFile( $src, $dst_file, \%opts );
}

#-------------------------------------------------------------------------------
# Function        : AUTOLOAD
#
# Description     : Intercept bad user directives and issue a nice error message
#                   This is a simple routine to report unknown user directives
#                   It does not attempt to distinguish between user errors and
#                   programming errors. It assumes that the program has been
#                   tested. The function simply report filename and line number
#                   of the bad directive.
#
# Inputs          : Original function arguments ( not used )
#
# Returns         : This function does not return
#
sub AUTOLOAD
{
    my $fname = $::AUTOLOAD;
    $fname =~ s~^\w+::~~;
    my ($package, $filename, $line) = caller;

    Error ("Directive not known or not allowed in this context: $fname",
           "Directive: $fname( @_ );",
           "File: $filename, Line: $line" );
}


#-------------------------------------------------------------------------------
# Function        : FirstBoot
#
# Description     : Add a file to the first boot section
#                   Assumes that the firstboot directory is /var/afc/firstboot
#                   This is the default
#
# Inputs          : All options
#                       --Debian=xxx[,opts] - Locate debian package
#                                             Sub opts are:
#                                               --Arch=xxx
#                                               --Product=xxx
#                                               --Debug
#                                               --Prod
#                       --File=yyy          - Locate a files
#                       --Level=nn          - Level to run (default=50)
#
# Returns         : 
#
sub FirstBoot
{
    my  $file;
    my  $level = 50;

    Error ("Day0Source must be specified first")
        unless ( $source_specified );
    
    #
    #   Collect user items
    #
    foreach ( @_ )
    {
        if ( m~^--Debian=(.+)~ ) {
            Error ("FirstBoot: Multiple source files not supported in one directive: $_") if $file;
            $file = Internal::LocateDebianFile($1, $opt_target, $opt_product);

        } elsif ( m~^--File=(.+)~ ) {
            Error ("FirstBoot: Multiple source files not supported in one directive: $_") if $file;
            $file = $1;

        } elsif ( m~^--Level=(\d+)~ ) {
            $level = $1;
            Error ("FirstBoot: Invalid Level: $_") if ($level < 0 || $level > 99);

        } else {
            Error ("FirstBoot: Unknown option or argument: $_");
        }
    }

    #
    #   Insert the required file
    #       Prepend name with a two digit level
    #
    my $fname = sprintf ('%2.2d.%s', $level, StripDir($file)  );
    Message ("FirstBoot: $fname");
    
    CopyFile ( $file, '/var/afc/firstboot', "--Rename=$fname" );
}

################################################################################
################################################################################
#   
#   Package to contain DayOp opereration
#
package Day0Ops;

use strict;
use warnings;


use JatsError;
use JatsSystem;
use JatsCopy qw(SetCopyDirDefaults DeleteDir CopyDir CreateDir);

#-------------------------------------------------------------------------------
# Function        : hostname
#
# Description     : Insert hostname information into the target
#                       Insert /etc/devicetype
#                       Insert into hostname too
#
#
# Inputs          : 
#
# Returns         : 
#
sub hostname
{
    Message ("Insert Hostname an Devicename: ${opt_product}");
    ::EchoFile ( "/etc/devicetype", $opt_product );
    ::EchoFile ( "/etc/hostname", $opt_product );
    ::EchoFile ( "/etc/day0-version", "$opt_buildname $opt_version $opt_product $opt_platform $opt_target" );
}

#-------------------------------------------------------------------------------
# Function        : setupbusybox
#
# Description     : Insert busybox into the skeleton
#                   Locate busybox and associated links file
#
# Inputs          : None
#
# Returns         : Nothing
#
sub setupbusybox
{
    #
    #   Insert busybox into the skeleton
    #   Locate busybox and associated links file
    #
    Message ("Copy in busybox");
    my $bbfile  = ::CopyPackage ('bin', 'busybox',       "--Dest=bin" );
    my $bblinks = ::CopyPackage ('bin', 'busybox.links', "--Dest=bin" );

    #
    #   Expand the busybox links
    #   Use soft links. Expect that they will be easier to update in the field
    #   Keep the .links file on the target.
    #
    Message "Expand busybox links";
    open ( BBLINK, "<", $bblinks ) || Error ("Cannot open BusyBox Links file");
    while ( <BBLINK> )
    {
        s~\s+~~;
        Internal::MakeSymLink ("/bin/busybox", $_, '--NoDotDot'  );
    }
    close BBLINK;

    #
    #   May want to convert busybox.links into a .LINKS file and process
    #   it when we instantiate the file system
    #

}

#-------------------------------------------------------------------------------
# Function        : upgrade
#
# Description     : Insert day0 upgrade components
#
# Inputs          : None
#
# Returns         : Nothing
#
sub upgrade
{
    Message ("Copy in day0 upgrade");
    my $DAY0BIN="/afc/day-zero/bin";       #/day-zero

    ::CopyPackage ('bin',     "--Dest=$DAY0BIN", 'udp_broadcast' );
    ::CopyPackage ('bin',     "--Dest=$DAY0BIN", 'udp_listen' );
    ::CopyPackage ('scripts', "--Dest=$DAY0BIN", 'day-zero.conf' );
    ::CopyPackage ('scripts', "--Dest=$DAY0BIN", 'day-zero.sh', 'day-zero' );

    #
    #   Start Day-Zero Service
    #   The service script is a part of the skeleton
    #
    ::AddInitScript ("day-zero"   , 95, 100-95, "--NoCopy" );
}

#-------------------------------------------------------------------------------
# Function        : dams
#
# Description     : Insert DAMS
#                   Only sutable for single exe versions
#
# Inputs          : 
#
# Returns         : 
#
sub dams
{
    Message ("Copy in DAMS");
    ::CopyPackage ('bin',     "--Dest=/afc/dams/bin", 'dams' );
}

#-------------------------------------------------------------------------------
# Function        : wirelesstools
#
# Description     : Insert wireless tools
#                   This is a multi-call binary
# Inputs          :
#
# Returns         : 
#
sub wirelesstools
{
    Message ("Copy in wireless tools");

    ::CopyPackage ('bin', 'ifrename'  , "--Dest=/sbin");
    ::CopyPackage ('bin', 'iwconfig'  , "--Dest=/sbin");
    ::CopyPackage ('bin', 'iwevent'   , "--Dest=/sbin");
    ::CopyPackage ('bin', 'iwgetid'   , "--Dest=/sbin");
    ::CopyPackage ('bin', 'iwlist'    , "--Dest=/sbin");
    ::CopyPackage ('bin', 'iwpriv'    , "--Dest=/sbin");
    ::CopyPackage ('bin', 'iwspy'     , "--Dest=/sbin");
}

#-------------------------------------------------------------------------------
# Function        : zd1211
#
# Description     : Copy in the zd1211 support files
#
# Inputs          : 
#
# Returns         : 
#

sub zd1211
{
    Message ("Copy in zd1211 firmware");
    ::CopyPkgDir  ( 'pkg/zd1211', '/lib/firmware/zd1211', '--FilterOut=READ*' );

    #
    #   Install the start up script
    #   This will be delivered by the kernel package
    #
    ::AddInitScript ("$opt_interfacedir/etc/zd1211", 41, 100-41 );
}

#-------------------------------------------------------------------------------
# Function        : kernelmodules
#
# Description     : Insert Kernel Modules
#                   Note: The EMU flavour does not have any kernel modules (yet)
#                          Allow for no files to be present or copied
#
# Inputs          : 
#
# Returns         : 
#
sub kernelmodules
{
    Message ("Copy in Kernel Modules");
    CopyDir ( "${opt_interfacedir}/bin", "$WORK/lib/modules",
                    'Flatten' => 1,
                    'Match' => [ '*.ko' ],
                    'Log' => 1,
                     );
}

#-------------------------------------------------------------------------------
# Function        : e2fsprogs
#
# Description     : Insert e2fsprogs
#                   Busybox 1.4.2 and later does not provide these utilities
#                   so they have been built up.
#
# Inputs          :
#
# Returns         : 
#
sub e2fsprogs
{
    #
    #   Only install the files that are needed
    #
    Message        ("Copy in e2fsprogs");
    ::CopyPackage    ('pkg', "/etc/mke2fs.conf");
    ::CopyPackage    ('pkg', "/sbin/fsck");
    ::CopyPackage    ('pkg', "/sbin/tune2fs"   ,"e2label"   ,"findfs");
    ::CopyPackage    ('pkg', "/sbin/mke2fs"    ,"mkfs.ext2" ,"mkfs.ext3");
    ::CopyPackage    ('pkg', "/sbin/e2fsck"    ,"fsck.ext2" ,"fsck.ext3");
    #::CopyPackage   ('pkg', "/sbin/badblocks");
    #::CopyPackage   ('pkg', "/sbin/blkid");
    #::CopyPackage   ('pkg', "/sbin/debugfs");
    #::CopyPackage   ('pkg', "/sbin/dumpe2fs");
    #::CopyPackage   ('pkg', "/sbin/logsave");
    ::CopyPackage    ('pkg', "/sbin/resize2fs");
}

#-------------------------------------------------------------------------------
# Function        : compilerruntime
#
# Description     : Copy in the runtime shared libraries provided by the compiler
#                       Many libraries are also symlinked
#                       Need to ensure that the linkis copied
#
#                       Strip many ( but not all) of the libaries
#                       Some are special
# Inputs          : 
#
# Returns         : 
#
sub compilerruntime
{
    Message ("Copy in Compiler Shared Libaries");
    my @slibs;
    JatsCopy::CopyDir( $CROSSLIBS, "$WORK/lib",
                            'Flatten' => 1,
                            'NoSubDirs' => 1,
                            'DuplicateLinks' => 1,
                            'Match' => [ '*.so', '*.so.*[0-9]' ],
                            'FileList' => \@slibs,
                            );


    foreach my $file (@slibs)
    {
        next if ( -d $file );
        next if ( -l $file );
        next if ( $file =~ m~libgcc_s.so$~ );
        next if ( $file =~ m~libc.so$~ );
        next if ( $file =~ m~libpthread.so$~ );
        next if ( $file =~ m~libthread_db.*\.so~ );         # Needs symbols

            Verbose ("Creating debug data: $file");
            System ($CROSS_OBJCOPY, '--only-keep-debug', $file, $file . '.dbg' );
        
            Verbose ("Stripping: $file");
            System ($CROSS_OBJCOPY, '--strip-all', $file);
        
            Verbose ("Linking debug to stripped library: $file");
            System ($CROSS_OBJCOPY, '--add-gnu-debuglink=' . $file . '.dbg', $file);
            
            Verbose ("Delecting debug file: $file" . ".dbg");
            System ("rm", "-f", $file . '.dbg'); 
    }

    #
    #   Copy in ldconfig
    #
    Message ("Copy in the ldconfig utility");
#    ::CopyFile ( "${CROSSEXE}/sbin/ldconfig", "/sbin" );
    ::CopyFile ( "${CROSSEXE}/sbin/ldconfig",          "/sbin" ) if (${CROSSEXE} =~ m~gcc-4\.1\.1-glibc-2\.5~);
    ::CopyFile ( "${CROSSEXE}/sys-root/sbin/ldconfig", "/sbin" ) if (${CROSSEXE} =~ m~gcc-4\.4\.3-glibc-2\.9~);
    # This is used for COBRA2 and VIPER2
    ::CopyFile ( "${CROSSEXE}/sysroot/sbin/ldconfig", "/sbin" ) if (${CROSSEXE} =~ m~gcc-5\.2\.0-glibc-2\.17~);
}

#-------------------------------------------------------------------------------
# Function        : grub
#
# Description     : Install grub related files, if requested by the user
#
# Inputs          : Options
#                       --Menu=file             - Found locally
#                       --Kernel=file           - Found in Bin
#
# Returns         : 
#
sub grub
{
    my $menu;
    my $kernel;

    Message ("Installing Grub");

    #
    #   Extract options
    #
    foreach ( @_ )
    {
        if ( m~^--Menu=(.+)~ ) {
            $menu = $1;
        } elsif ( m~^--Kernel=(.+)~ ) {
            $kernel = $1;
        } else {
            Error ("Day0 Grub: Unknown option: $_");
        }
    }

    Error ("Day0 Grub: No Menu file specified") unless ( $menu );
    Error ("Day0 Grub: No Kernel file specified") unless ( $kernel );

    ::CopyPackage ( 'pkg', '/boot/grub/grub');                     # Not really needed
    ::CopyPackage ( 'pkg', '/boot/grub/stage1');                   # These are ..
    ::CopyPackage ( 'pkg', '/boot/grub/stage2');
    ::CopyPackage ( 'pkg', '/boot/grub/e2fs_stage1_5');

    #
    #   Copy in the user specified boot related files
    #
    ::CopyFile   ( $menu,  '/boot/grub' );
    ::CopyPackage ('bin', $kernel, '--Dest=/boot');
    
}



################################################################################
################################################################################
#
#   Package to contain Internal functions
#   Functions placed here are not available to the user in the user scripts
#   They have been placed here simply to hide them from the user
#
#
package Internal;

use strict;
use warnings;

use File::Find;
use JatsError;
use FileUtils;

#-------------------------------------------------------------------------------
# Function        : MakeSymLink
#
# Description     : Create a symlink - with error detection
#
# Inputs          : old_file    - Link Target
#                                 Path to the link target
#                                 If an ABS path is provided, the routine will
#                                 attempt to create a relative link.
#                   new_file    - Relative to the output work space
#                                 Path to where the 'link' file will be created
#                   Options     - Must be last
#                                 --NoClean         - Don't play with links
#                                 --NoDotDot        - Don't create symlinks with ..
#
# Returns         : Nothing
#
sub MakeSymLink
{
    my $no_clean;
    my $no_dot;
    my @args;

    #
    #   Extract options
    #
    foreach ( @_ )
    {
        if ( m/^--NoClean/i ) {
            $no_clean = 1;

        } elsif ( m/^--NoDotDot/i ) {
            $no_dot = 1;

        } elsif ( m/^--/ ) {
            Error ("MakeSymLink: Unknown option: $_");

        } else {
            push @args, $_;
        }
    }

    my ($old_file, $new_file) = @args;

    my $tfile = $WORK . '/' . $new_file;
    $tfile =~ s~//~/~;
    Verbose ("Symlink $old_file -> $new_file" );

    #
    #   Create the directory in which the link will be placed
    #   Remove any existing file of the same name
    #
    my $dir = StripFileExt( $tfile );
    mkdir $dir unless -d $dir;
    unlink $tfile;

    #
    #   Determine a good name of the link
    #   Convert to a relative link in an attempt to prune them
    #
    my $sfile = $old_file;
    unless ( $no_clean )
    {
        $sfile = CalcRelPath( StripFileExt( $new_file ), $old_file );
        if ( $no_dot && $sfile =~ m~^../~ )
        {
            $sfile = $old_file;
        }
    }

    my $result = symlink $sfile, $tfile;
    Error ("Cannot create symlink. $old_file -> $new_file") unless ( $result );
}

#-------------------------------------------------------------------------------
# Function        : LocateFile
#
# Description     : Locate a file within the interface directory
#                   These files will have come from an external package
#
# Inputs          : $section            One of: bin, lib, pkg, scripts
#                                       Will be translated into a suitable path
#                   $file               Path to file
#
# Returns         : Full path to the file
#                   Will return 'undef' if not found
#                   The user must generate the error
#
sub LocateFile
{
    my ($section, $file) = @_;
    my $base = "$opt_interfacedir/$section";

    unless ( -d $base )
    {
        Warning("LocateFile: Section not found: $section");
        return undef;
    }

    my @done;
    my @parts = GetBuildParts ($opt_interfacedir, $opt_platform );
    push @parts, '';
    foreach my $type ( $opt_type, '' )
    {
        foreach my $subdir ( @parts )
        {
            my $sfile = "$base/$subdir$type/$file";
            $sfile =~ s~//~/~g;
            Verbose2("LocateFile: $sfile");
            if ( -f $sfile )
            {
                unless ( @done )
                {
                    push @done, $sfile;
                }
            }
        }
    }

    if ( $#done > 0)
    {
        Warning ("LocateFile: Multiple instances of file found. Only first is used", @done);
    }
    return $done[0];
}

#-------------------------------------------------------------------------------
# Function        : LocateDebianFile
#
# Description     : Locate a debian file
#                   Internal Function
#
#                   Scan packages for the Debian package specified
#                   The user provides the base name of the package
#                   A Debian Package name has several fields
#                   These are:
#                       1) Base Name - Provided by the user
#                       2) Version - Version will be wildcarded
#                       3) Architecture - Wildcarded. Uses bin/arch directory
#                   
#                   Expect to find Debian Packages in the bin/PLATFORM subdir
#
# Inputs          : Debian base name, complete with suboptions
#
# Returns         : Full path of the file
#
sub LocateDebianFile
{
    my ($arg, $arch, $product) = @_;
    Verbose("LocateDebianFile: Processing: $arg");

    my @type = qw( P D );
    my %debian_file_path;
    
    #
    #   Extract sub-options
    #       --Prod[uction]
    #       --Debug
    #       --Arch[itecture]=yyy
    #
    my ($base_name, @opts) = split( ',', $arg );
    foreach ( @opts )
    {
        if ( m/^--Arch(.*)=(.+)/ ) {
            $arch=$2;
        } elsif ( m/^--Product=(.+)/ ) {
            $product=$1;
        } elsif ( m/^--Prod/ ) {
            @type = 'P';
        } elsif ( m/^--Debug/ ) {
            @type = 'D';
        }
    }

    #
    #   Create a list of products
    #   ie: PRODUCT_ARCH
    #
    my @products;
    push @products, $product . '_' . $arch if ( $product );
    push @products, $arch;

    #
    #   Scan all packages for the specified debian package
    #
    foreach my $package_dir ( $opt_interfacedir )
    {
        foreach my $type ( @type )
        {
            foreach my $prd ( @products )
            {
                foreach my $joiner ( qw(/ .) )
                {
                    my $dir = "$package_dir/bin$joiner$prd$type";
                    Verbose("LocateDebianFile: Search in $dir");
                    next unless ( -d $dir );
                    my @files = glob ( "$dir/${base_name}_*.deb" );
                    next unless ( @files );
                    push @{$debian_file_path{$type}}, @files;
                }
            }
        }
    }

    Error ("Required Debain package not found: $base_name",) unless %debian_file_path;

    #
    #   Select 'preferred' type of file
    #   If we are doing a debug build, then prefer debug package
    #   If not available then use any available
    #
    my @debian_file_path = @{$debian_file_path{$opt_type}};
    if ( ! @debian_file_path )
    {
        foreach ( keys %debian_file_path )
        {
            push @debian_file_path,@{$debian_file_path{$_}};
        }
    }

    Error ("Multiple matching Debian Packages located", @debian_file_path)
        if ( $#debian_file_path > 0 );

    return $debian_file_path[0];
}

#-------------------------------------------------------------------------------
# Function        : CalcRelPath
#
# Description     : Return the relative path to the current working directory
#                   as provided in $Cwd
#
# Inputs          : $Cwd - Base dir
#                   $base - Path to convert
#
# Returns         : Relative path from the $Cwd
#
sub CalcRelPath
{
    my ($Cwd, $base) = @_;

    my @base = split ('/', $base );
    my @here = split ('/', $Cwd );
    my $result;

    Debug("CalcRelPath: Source: $base");

    return $base unless ( $base =~ m~^/~ );
    
    #
    #   Remove common bits from the head of both lists
    #
    while ( $#base >= 0 && $#here >= 0 && $base[0] eq $here[0] )
    {
        shift @base;
        shift @here;
    }

    #
    #   Need to go up some directories from here and then down into base
    #
    $result = '../' x ($#here + 1);
    $result .= join ( '/', @base);
    $result = '.' unless ( $result );
    $result =~ s~//~/~g;
    $result =~ s~/$~~;

    Debug("CalcRelPath: Result: $result");
    return $result;
}

#-------------------------------------------------------------------------------
# Function        : GetBuildParts
#
# Description     : Determine the 'parts' of the build for the current platform
#                   This is complex information. It is held within the build.cfg
#                   file.
#
# Inputs          : $interface_dir          - Path to the interface dir
#                   $platform               - Platform to process
#
# Returns         : An array of platform parts
#

#
#   The following varaibles are "read" in from the build.cfg file
#   In order to access them we need to declare them
#
our %ScmBuildPkgRules;
our %BUILDPLATFORM_PARTS;

sub GetBuildParts
{
    my ($interface_dir, $platform) = @_;

    #
    #   Tthe build.cfg file is within the interface directory
    #
    my $cfgfile = "$interface_dir/build.cfg";
    Error ("Cannot find file: $cfgfile" ) unless ( -f $cfgfile );

    #
    #   Include the build.cfg data
    #
    require ( $cfgfile );

    #
    #   Extract the platform parts
    #
    if ( exists  $BUILDPLATFORM_PARTS{$platform})
    {
        return @{$BUILDPLATFORM_PARTS{$platform}};
    }
    else
    {
        Error ("Platform not found in build.cfg: $platform");
    }
}

#-------------------------------------------------------------------------------
# Function        : FindFiles
#
# Description     : Locate files within a given dir tree
#
# Inputs          : $root           - Base of the search
#                   $match          - Re to match
#
# Returns         : A list of files that match
#
my @FIND_LIST;
my $FIND_NAME;

sub FindFiles
{
    my ($root, $match ) = @_;
    Verbose2("FindFiles: Root: $root, Match: $match");

    #
    #   Becareful of closure, Must use globals
    #
    @FIND_LIST = ();
    $FIND_NAME = $match;
    File::Find::find( \&find_files, $root);

    #
    #   Find callback program
    #
    sub find_files
    {
        my $item =  $File::Find::name;
        return if ( -d $_ );
        return unless ( $_ =~ m~$FIND_NAME~ );
        push @FIND_LIST, $item;
    }
    return @FIND_LIST;
}