# Sumavi Inc (C) 2010

# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html

#####################################################
# imgport will export and import xCAT stateless, statelite, and diskful templates.
# This will make it so that you can easily share your images with others.
# All your images are belong to us!
package xCAT_plugin::imgport;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use warnings;

#use xCAT::Table;
#use xCAT::Schema;
#use xCAT::NodeRange qw/noderange abbreviate_noderange/;
use xCAT::Utils;
use xCAT::TableUtils;
use Data::Dumper;
use XML::Simple;
use POSIX qw/strftime/;
use Getopt::Long;
use File::Temp;
use File::Copy;
use File::Path qw/mkpath/;
use File::Path qw/rmtree/;
use File::Basename;
use xCAT::NodeRange;
use xCAT::Schema;
use xCAT::SvrUtils;
use Cwd;
my $requestcommand;
$::VERBOSE = 0;
my $hasplugin = 0;

1;

#some quick aliases to table/value
my %shortnames = (
    groups => [qw(nodelist groups)],
    tags   => [qw(nodelist groups)],
    mgt    => [qw(nodehm mgt)],

    #switch => [qw(switch switch)],
);

#####################################################
# Return list of commands handled by this plugin
#####################################################
sub handled_commands
{
    return {
        imgexport => "imgport",
        imgimport => "imgport",
    };
}

#####################################################
# Process the command
#####################################################
sub process_request
{
    #use Getopt::Long;
    Getopt::Long::Configure("bundling");

    #Getopt::Long::Configure("pass_through");
    Getopt::Long::Configure("no_pass_through");

    my $request  = shift;
    my $callback = shift;
    $requestcommand = shift;
    my $command = $request->{command}->[0];
    my $args    = $request->{arg};

    if ($command eq "imgexport") {
        return xexport($request, $callback);
    } elsif ($command eq "imgimport") {
        return ximport($request, $callback);
    } else {
        print "Error: $command not found in export\n";
        $callback->({ error => ["Error: $command not found in this module."], errorcode => [1] });

        #return (1, "$command not found in sumavinode");
    }
}

# extract the bundle, then add it to the osimage table.  Basically the ying of the yang of the xexport
# function.
sub ximport {
    my $request  = shift;
    my $callback = shift;
    my %rsp;    # response
    my $help;
    my $nodes;
    my $new_profile;
    my $remoteHost;
    my $nozip = 0;

    my $xusage = sub {
        my $ec = shift;
        push @{ $rsp{data} }, "imgimport: Takes in an xCAT image bundle and defines it to xCAT so you can use it";
        push @{ $rsp{data} }, "Usage: ";
        push @{ $rsp{data} }, "\timgimport [-h|--help]";
        push @{ $rsp{data} }, "\timgimport <bundle_file_name> [-p|--postscripts <nodelist>] [-f|--profile <new_profile>] [-R|--remotehost <userid\@host>] [-n|--nozip] [-v]";
        if ($ec) { $rsp{errorcode} = $ec; }
        $callback->(\%rsp);
    };
    unless (defined($request->{arg})) { $xusage->(1); return; }
    @ARGV = @{ $request->{arg} };
    if ($#ARGV == -1) {
        $xusage->(1);
        return;
    }

    GetOptions(
        'h|?|help'        => \$help,
        'v|verbose'       => \$::VERBOSE,
        'R|remotehost=s'  => \$remoteHost,
        'p|postscripts=s' => \$nodes,
        'f|profile=s'     => \$new_profile,
        'n|nozip'         => \$nozip
    );

    if ($help) {
        $xusage->(0);
        return;
    }

    if ($#ARGV == -1) {
        # if no arguments left after processing the options, then bundle name is missing
        $xusage->(1);
    } else {
        # first extract the bundle
        my $bundle = shift @ARGV;
        extract_bundle($request, $callback, $bundle, $nodes, $new_profile, $remoteHost, $nozip );
    }

}


# function to export your image.  The image should already be in production, work well, and have
# no bugs.  Lots of places will have problems because the image may not be in osimage table
# or they may have hardcoded things, or have post install scripts.
sub xexport {
    my $request  = shift;
    my $callback = shift;
    my %rsp;    # response
    my $help;
    my @extra;
    my $node;
    my $remoteHost;

    my $xusage = sub {
        my $ec = shift;
        push @{ $rsp{data} }, "imgexport: Creates a tarball (bundle) of an existing xCAT image";
        push @{ $rsp{data} }, "Usage: ";
        push @{ $rsp{data} }, "\timgexport [-h|--help]";
        push @{ $rsp{data} }, "\timgexport <image_name> [destination] [[-e|--extra <file:dir> ] ... ] [-p|--postscripts <node_name>] [-R|--remotehost <userid\@host>] [-v]";
        if ($ec) { $rsp{errorcode} = $ec; }
        $callback->(\%rsp);
    };
    unless (defined($request->{arg})) { $xusage->(1); return; }
    @ARGV = @{ $request->{arg} };
    if ($#ARGV == -1) {
        $xusage->(1);
        return;
    }

    GetOptions(
        'h|?|help'        => \$help,
        'p|postscripts=s' => \$node,
        'e|extra=s'       => \@extra,
        'R|remotehost=s'  => \$remoteHost,
        'v|verbose'       => \$::VERBOSE
    );

    if ($help) {
        $xusage->(0);
        return;
    }

    # ok, we're done with all that.  Now lets actually start doing some work.
    my $img_name = shift @ARGV;
    my $dest     = shift @ARGV;
    my $cwd      = $request->{cwd};    #getcwd;
    $cwd = $cwd->[0];

    if (!defined $remoteHost) {
        $callback->({ data => ["Exporting $img_name to $cwd..."] });
    }
    else {
        $callback->({ data => ["Exporting $img_name to $remoteHost..."] });
    }


    # check if all files are in place
    my $attrs = get_image_info($img_name, $callback, $node, @extra);

    #print Dumper($attrs);

    unless ($attrs) {
        return 1;
    }

    # make manifest and tar it up.
    make_bundle($img_name, $dest, $remoteHost, $attrs, $callback, $cwd);

}





# verify the image and return the values
sub get_image_info {
    my $imagename = shift;
    my $callback  = shift;
    my $node      = shift;
    my @extra     = @_;
    my $errors    = 0;
    my $attrs;

    my $ostab = new xCAT::Table('osimage', -create => 1);
    unless ($ostab) {
        $callback->(
            { error => ["Unable to open table 'osimage'."], errorcode => 1 }
        );
        return 0;
    }

    #(my $attrs) = $ostab->getAttribs({imagename => $imagename}, 'profile', 'imagetype', 'provmethod', 'osname', 'osvers', 'osdistro', 'osarch', 'synclists');
    (my $attrs0) = $ostab->getAttribs({ imagename => $imagename }, \@{ $xCAT::Schema::tabspec{osimage}->{cols} });
    if (!$attrs0) {
        $callback->({ error => ["Cannot find image \'$imagename\' from the osimage table."], errorcode => [1] });
        return 0;
    }

    unless ($attrs0->{provmethod}) {
        $callback->({ error => ["The 'provmethod' field is not set for \'$imagename\' in the osimage table."], errorcode => [1] });
        $errors++;
    }

    unless ($attrs0->{profile}) {
        $callback->({ error => ["The 'profile' field is not set for \'$imagename\' in the osimage table."], errorcode => [1] });
        $errors++;
    }

    unless ($attrs0->{osvers}) {
        $callback->({ error => ["The 'osvers' field is not set for \'$imagename\' in the osimage table."], errorcode => [1] });
        $errors++;
    }

    unless ($attrs0->{osarch}) {
        $callback->({ error => ["The 'osarch' field is not set for \'$imagename\' in the osimage table."], errorcode => [1] });
        $errors++;
    }

    unless ($attrs0->{provmethod} =~ /install|netboot|statelite|raw|sysclone/) {
        $callback->({ error => [ "Exporting images with 'provemethod' " . $attrs0->{provmethod} . " is not supported. Hint: install, netboot, statelite, or raw" ], errorcode => [1] });
        $errors++;
    }

    if (($attrs0->{provmethod} =~ /sysclone/) && ($attrs0->{osarch} !~ /s390x/)) {
        $callback->({ error => [ "Exporting images with 'provemethod' " . $attrs0->{provmethod} . " is not supported for osarch '" . $attrs0->{osarch} . "'" ], errorcode => [1] });
        $errors++;
    }

    #$attrs->{imagename} = $imagename;

    if ($errors) {
        return 0;
    }

    $attrs->{osimage} = $attrs0;

    my $linuximagetab = new xCAT::Table('linuximage', -create => 1);
    unless ($linuximagetab) {
        $callback->(
            { error => ["Unable to open table 'linuximage'"], errorcode => 1 }
        );
        return 0;
    }

    #from linuximage table
    #(my $attrs1) = $linuximagetab->getAttribs({imagename => $imagename}, 'template', 'pkglist', 'pkgdir', 'otherpkglist', 'otherpkgdir', 'exlist', 'postinstall', 'rootimgdir', 'nodebootif', 'otherifce', 'netdrivers', 'kernelver', 'permission');
    (my $attrs1) = $linuximagetab->getAttribs({ imagename => $imagename }, \@{ $xCAT::Schema::tabspec{linuximage}->{cols} });
    if (!$attrs1) {
        $callback->({ error => ["Cannot find image \'$imagename\' from the linuximage table."], errorcode => [1] });
        return 0;
    }

    #merge attrs with attrs1
    #foreach (keys %$attrs1) {
    #    $attrs->{$_} = $attrs1->{$_};
    #}
    $attrs->{linuximage} = $attrs1;


    # for kit staff

    if ($attrs0->{kitcomponents}) {
        my $kitcomponenttab = new xCAT::Table('kitcomponent', -create => 1);
        unless ($kitcomponenttab) {
            $callback->(
                { error => ["Unable to open table 'kitcomponent'"], errorcode => 1 }
            );
            return 0;
        }

        my $kittab = new xCAT::Table('kit', -create => 1);
        unless ($kittab) {
            $callback->(
                { error => ["Unable to open table 'kit'"], errorcode => 1 }
            );
            return 0;
        }

        my $kitrepotab = new xCAT::Table('kitrepo', -create => 1);
        unless ($kitrepotab) {
            $callback->(
                { error => ["Unable to open table 'kitrepo'"], errorcode => 1 }
            );
            return 0;
        }

        my $kitlist;
        my $kitrepolist;
        my $kitcomplist;
        my $DBname = xCAT::Utils->get_DBName;    # support for DB2
        foreach my $kitcomponent (split ',', $attrs0->{kitcomponents}) {
            (my $kitcomphash) = $kitcomponenttab->getAttribs({ kitcompname => $kitcomponent }, 'kitname');
            if (!$kitcomphash) {
                $callback->({ error => ["Cannot find kitname of \'$kitcomponent\' from the kitcomponent table."], errorcode => [1] });
                return 0;
            }

            if ($kitcomphash->{kitname}) {
                $kitlist->{ $kitcomphash->{kitname} } = 1;
                my @kitrepohash;
                if ($DBname =~ /^DB2/) {
                    @kitrepohash = $kitrepotab->getAllAttribsWhere("\"kitname\" = '$kitcomphash->{kitname}'", 'kitreponame');
                } else {
                    @kitrepohash = $kitrepotab->getAllAttribsWhere("kitname = '$kitcomphash->{kitname}'", 'kitreponame');
                }
                foreach my $kitrepo (@kitrepohash) {
                    if ($kitrepo->{kitreponame}) {
                        $kitrepolist->{ $kitrepo->{kitreponame} } = 1;
                    }
                }

                my @kitcomponents;
                if ($DBname =~ /^DB2/) {
                    @kitcomponents = $kitcomponenttab->getAllAttribsWhere("\"kitname\" = '$kitcomphash->{kitname}'", 'kitcompname');
                } else {
                    @kitcomponents = $kitcomponenttab->getAllAttribsWhere("kitname = '$kitcomphash->{kitname}'", 'kitcompname');
                }
                foreach my $kitcomp (@kitcomponents) {
                    if ($kitcomp->{kitcompname}) {
                        $kitcomplist->{ $kitcomp->{kitcompname} } = 1;
                    }
                }
            }
        }


        foreach my $kitname (keys %$kitlist) {
            (my $kitattrs) = $kittab->getAttribs({ kitname => $kitname }, \@{ $xCAT::Schema::tabspec{kit}->{cols} });
            if (!$kitattrs) {
                $callback->({ error => ["Cannot find kit \'$kitname\' from the kit table."], errorcode => [1] });
                return 0;
            }

            $attrs->{kit}->{$kitname} = $kitattrs;
        }

        foreach my $kitreponame (keys %$kitrepolist) {
            (my $kitrepoattrs) = $kitrepotab->getAttribs({ kitreponame => $kitreponame }, \@{ $xCAT::Schema::tabspec{kitrepo}->{cols} });
            if (!$kitrepoattrs) {
                $callback->({ error => ["Cannot find kitrepo \'$kitreponame\' from the kitrepo table."], errorcode => [1] });
                return 0;
            }

            $attrs->{kitrepo}->{$kitreponame} = $kitrepoattrs;
        }

        foreach my $kitcompname (keys %$kitcomplist) {
            (my $kitcompattrs) = $kitcomponenttab->getAttribs({ kitcompname => $kitcompname }, \@{ $xCAT::Schema::tabspec{kitcomponent}->{cols} });
            if (!$kitcompattrs) {
                $callback->({ error => ["Cannot find kitcomp \'$kitcompname\' from the kitcomp table."], errorcode => [1] });
                return 0;
            }

            $attrs->{kitcomp}->{$kitcompname} = $kitcompattrs;
        }

    }



    $attrs = get_files($imagename, $callback, $attrs);
    if ($#extra > -1) {
        my $ex = get_extra($callback, @extra);
        if ($ex) {
            $attrs->{extra} = $ex;
        }
    }

    #get postscripts
    if ($node) {
        $attrs = get_postscripts($node, $callback, $attrs)
    }


    # if we get nothing back, then we couldn't find the files.  How sad, return nuthin'
    return $attrs;

}

sub get_postscripts {
    my $node     = shift;
    my $errors   = 0;
    my $callback = shift;
    my $attrs    = shift;
    my @nodes    = noderange($node);
    if (@nodes > 0) { $node = $nodes[0]; }
    else {
        $callback->(
            { error => ["Unable to get postscripts, $node is not a valide node."], errorcode => 1 }
        );
        return 0;
    }
    my $postscripts;
    my $postbootscripts;
    my $ptab = new xCAT::Table('postscripts', -create => 1);
    unless ($ptab) {
        $callback->(
            { error => ["Unable to open table 'postscripts'."], errorcode => 1 }
        );
        return 0;
    }

    my $ent = $ptab->getNodeAttribs($node, [ 'postscripts', 'postbootscripts' ]);
    if ($ent)
    {
        if ($ent->{postscripts}) { $postscripts = $ent->{postscripts}; }
        if ($ent->{postbootscripts}) { $postbootscripts = $ent->{postbootscripts}; }
    }

    (my $attrs1) = $ptab->getAttribs({ node => "xcatdefaults" }, 'postscripts', 'postbootscripts');
    if ($attrs1) {
        if ($attrs1->{postscripts}) {
            if ($postscripts) {
                $postscripts = $attrs1->{postscripts} . ",$postscripts";
            } else {
                $postscripts = $attrs1->{postscripts};
            }
        }
        if ($attrs1->{postbootscripts}) {
            if ($postbootscripts) {
                $postbootscripts = $attrs1->{postbootscripts} . ",$postbootscripts";
            } else {
                $postbootscripts = $attrs1->{postbootscripts};
            }
        }

    }
    if ($postscripts) {
        $attrs->{postscripts} = $postscripts;
    }
    if ($postbootscripts) {
        $attrs->{postbootscripts} = $postbootscripts;
    }
    return $attrs;
}


# returns a hash of files
# extra {
#   file => dir
#   file => dir
# }

sub get_extra {
    my $callback = shift;
    my @extra    = @_;
    my $extra;

    # make sure that the extra is formatted correctly:
    foreach my $e (@extra) {
        my ($file, $to_dir) = split(/:/, $e);
        unless (-r $file) {
            $callback->({ error => ["Can not find Extra file $file.  Argument will be ignored"], errorcode => [1] });
            next;
        }

        #print "$file => $to_dir";
        if (!$to_dir) {
            if (-d $file) {
                $to_dir = $file;
            } else {
                $to_dir = dirname($file);
            }
        }
        push @{$extra}, { 'src' => $file, 'dest' => $to_dir };
    }
    return $extra;
}



# well we check to make sure the files exist and then we return them.
sub get_files {
    my $imagename = shift;
    my $errors    = 0;
    my $callback  = shift;
    my $attrs = shift; # we'll hopefully get a reference to it and modify this variable.
    my @arr;           # array of directory search paths
    my $template = '';

    # todo is XCATROOT not going to be /opt/xcat/  in normal situations?  We'll always
    # assume it is for now
    my $xcatroot = "/opt/xcat";

    # get the install root
    my $installroot = xCAT::TableUtils->getInstallDir();
    unless ($installroot) {
        $installroot = '/install';
    }

    my $provmethod = $attrs->{osimage}->{provmethod};
    my $osvers     = $attrs->{osimage}->{osvers};
    my $arch       = $attrs->{osimage}->{osarch};
    my $profile    = $attrs->{osimage}->{profile};

    # here's the case for the install.  All we need at first is the
    # template.  That should do it.
    if ($provmethod =~ /install/) {
        @arr = ("$installroot/custom/install", "$xcatroot/share/xcat/install");

        #get .tmpl file
        if (!$attrs->{linuximage}->{template}) {
            my $template = look_for_file('tmpl', $callback, $attrs, @arr);
            unless ($template) {
                $callback->({ error => ["Couldn't find install template for $imagename"], errorcode => [1] });
                $errors++;
            } else {
                $callback->({ data => ["$template"] });
                $attrs->{linuximage}->{template} = $template;
            }
        }
        $attrs->{media} = "required";
    }


    # for stateless I need to save the
    # ramdisk
    # the kernel
    # the rootimg.cpio.xz or rootimg.cpio.gz or rootimg.tar.xz or rootimg.tar.gz or rootimg.gz
    if ($osvers !~ /esx/) {

        # don't do anything because these files don't exist for ESX stateless.
        if ($provmethod =~ /netboot/) {

            # For 's390x', we want all of the image files
            if ($arch =~ /s390x/) {
                my @files;
                my $dir = "$installroot/netboot/$osvers/s390x/$profile";
                opendir(DIR, $dir) or $callback->({ error => ["Could not open image files in directory $dir"], errorcode => [1] });

                while (my $file = readdir(DIR)) {

                    # We only want files in the directory that end with .img
                    next unless (-f "$dir/$file");
                    next unless ($file =~ m/\.img$/);
                    push(@files, "$dir/$file");
                }

                if (@files) {
                    $attrs->{rawimagefiles}->{files} = [@files];
                }

                closedir(DIR);
            }
            else {
                @arr = ("$installroot/custom/netboot", "$xcatroot/share/xcat/netboot");

                #get .pkglist file
                if (!$attrs->{linuximage}->{pkglist}) {

                    # we need to get the .pkglist for this one!
                    my $temp = look_for_file('pkglist', $callback, $attrs, @arr);
                    unless ($temp) {
                        $callback->({ error => ["Couldn't find pkglist file for $imagename"], errorcode => [1] });
                        $errors++;
                    } else {
                        $attrs->{linuximage}->{pkglist} = $temp;
                    }
                }

                @arr = ("$installroot/netboot");
                my $rootimgdir = $attrs->{linuximage}->{rootimgdir};
                my $ramdisk;
                my $kernel;
                my $rootimg;

                # look for ramdisk, kernel and rootimg.cpio.xz | rootimg.cpio.gz | rootimg.tar.xz | rootimg.tar.gz | rootimg.gz
                if ($rootimgdir) {
                    if (-f "$rootimgdir/initrd-stateless.gz") {
                        $ramdisk = "$rootimgdir/initrd-stateless.gz";
                    }
                    if (-f "$rootimgdir/kernel") {
                        $kernel = "$rootimgdir/kernel";
                    }

                    my $compressedrootimg=xCAT::SvrUtils->searchcompressedrootimg("$rootimgdir");
                    $rootimg = "$rootimgdir/$compressedrootimg";

                } else {
                    $ramdisk = look_for_file('initrd-stateless.gz', $callback, $attrs, @arr);
                    $kernel = look_for_file('kernel', $callback, $attrs, @arr);

                    my @rootimg_array = ("rootimg.cpio.xz","rootimg.cpio.gz","rootimg.tar.xz","rootimg.tar.gz","rootimg.gz");
                    foreach my $ra (@rootimg_array) {
                        $rootimg = look_for_file("$ra", $callback, $attrs, @arr);
                        if ($rootimg) {
                            last;
                        }
                    }
                }
                unless ($ramdisk) {
                    $callback->({ error => ["Couldn't find ramdisk (initrd-stateless.gz) for  $imagename"], errorcode => [1] });
                    $errors++;
                } else {
                    $attrs->{ramdisk} = $ramdisk;
                }

                unless ($kernel) {
                    $callback->({ error => ["Couldn't find kernel (kernel) for  $imagename"], errorcode => [1] });
                    $errors++;
                } else {
                    $attrs->{kernel} = $kernel;
                }

                unless ($rootimg) {
                    $callback->({ error => ["Couldn't find compressed rootimg for $imagename"], errorcode => [1] });
                    $errors++;
                } else {
                    $attrs->{rootimg} = $rootimg;
                }
            }
        } elsif ($provmethod =~ /statelite/) {
            @arr = ("$installroot/custom/netboot", "$xcatroot/share/xcat/netboot");

            #get .pkglist file
            if (!$attrs->{linuximage}->{pkglist}) {

                # we need to get the .pkglist for this one!
                my $temp = look_for_file('pkglist', $callback, $attrs, @arr);
                unless ($temp) {
                    $callback->({ error => ["Couldn't find pkglist file for $imagename"], errorcode => [1] });
                    $errors++;
                } else {
                    $attrs->{linuximage}->{pkglist} = $temp;
                }
            }

            @arr = ("$installroot/netboot");
            my $rootimgdir = $attrs->{linuximage}->{rootimgdir};
            my $kernel;
            my $ramdisk;

            #look for kernel and ramdisk
            if ($rootimgdir) {
                if (-f "$rootimgdir/kernel") {
                    $kernel = "$rootimgdir/kernel";
                }
                if (-f "$rootimgdir/initrd-statelite.gz") {
                    $ramdisk = "$rootimgdir/initrd-statelite.gz";
                }
            } else {
                $kernel = look_for_file('kernel', $callback, $attrs, @arr);
                $ramdisk = look_for_file('initrd-statelite.gz', $callback, $attrs, @arr);
            }

            unless ($kernel) {
                $callback->({ error => ["Couldn't find kernel (kernel) for  $imagename"], errorcode => [1] });
                $errors++;
            } else {
                $attrs->{kernel} = $kernel;
            }

            unless ($ramdisk) {
                $callback->({ error => ["Couldn't find ramdisk (initrd-statelite.gz) for  $imagename"], errorcode => [1] });
                $errors++;
            } else {
                $attrs->{ramdisk} = $ramdisk;
            }
        }
    }

    if (($provmethod =~ /raw|sysclone/) and ($arch =~ /s390x/)) {
        my @files;
        my $dir = "$installroot/$provmethod/$osvers/s390x/$profile";
        opendir(DIR, $dir) or $callback->({ error => ["Could not open image files in directory $dir"], errorcode => [1] });

        while (my $file = readdir(DIR)) {

            # We only want files in the directory that end with .img
            next unless (-f "$dir/$file");
            next unless ($file =~ m/\.img$/);
            push(@files, "$dir/$file");
        }

        if (@files) {
            $attrs->{rawimagefiles}->{files} = [@files];
        }

        closedir(DIR);
    }

    if ($errors) {
        $attrs = 0;
    }
    return $attrs;
}


# argument:
# type of file:  This is usually the suffix of the file, or the file name.
# attributes:  These are the paramaters you got from the osimage table in a hash.
# @dirs:  Some search paths where we'll start looking for them.
# then we just return a string of the full path to where the file is.
# mostly because we just ooze awesomeness.
sub look_for_file {
    my $file     = shift;
    my $callback = shift;
    my $attrs    = shift;
    my @dirs     = @_;
    my $r_file   = '';

    my $profile  = $attrs->{osimage}->{profile};
    my $arch     = $attrs->{osimage}->{osarch};
    my $distname = $attrs->{osimage}->{osvers};


    # go through the directories and look for the file.  We hopefully will find it...
    foreach my $d (@dirs) {

        # widdle down rhel5.4, rhel5., rhel5, rhel, rhe, rh, r,
        my $dd = $distname; # dd is distro directory, or disco dave, whichever you prefer.
        if ($dd =~ /win/) { $dd = 'windows' }
        until (-r "$d/$dd" or not $dd) {
            $callback->({ data => ["not in  $d/$dd..."] }) if $::VERBOSE;
            chop($dd);
        }
        if ($distname && (($file eq 'tmpl') || ($file eq 'pkglist'))) {
            $callback->({ data => ["looking in $d/$dd..."] }) if $::VERBOSE;

            # now look for the file name: foo.rhel5.x86_64.tmpl
            (-r "$d/$dd/$profile.$distname.$arch.$file") && (return "$d/$dd/$profile.$distname.$arch.$file");

            # now look for the file name: foo.rhel5.tmpl
            (-r "$d/$dd/$profile.$distname.$file") && (return "$d/$dd/$profile.$distname.$file");

            # now look for the file name: foo.x86_64.tmpl
            (-r "$d/$dd/$profile.$arch.$file") && (return "$d/$dd/$profile.$arch.$file");

            # finally, look for the file name: foo.tmpl
            (-r "$d/$dd/$profile.$file") && (return "$d/$dd/$profile.$file");
        } else {

            # this may find the ramdisk: /install/netboot/
            (-r "$d/$dd/$arch/$profile/$file") && (return "$d/$dd/$arch/$profile/$file");
        }
    }

    # I got nothing man.  Can't find it.  Sorry 'bout that.
    # returning nothing:
    return '';
}


#-------------------------------------------------------

=head3   make_bundle

    Description : Makes the image bundle tar ball.
    Arguments   : Image name from the command line
                  Destination command line parameter, i.e. name of the tar ball.  For a
                    remote host this can include the target directory.
                  Remote host parameter from the command line, if specified
                  Image attributes from the osimage table
                  callback
                  Current working directory
    Returns     : None
    Example     : make_bundle( $img_name, $dest, $remoteHost, $attrs, $callback, $cwd );

=cut

#-------------------------------------------------------
sub make_bundle {
    my $imagename  = shift;
    my $dest       = shift;
    my $remoteHost = shift;
    my $attribs    = shift;
    my $callback   = shift;
    my $dir        = shift;
    my $rc;

    # Determine the local working directory.  It could be specified in the site table
    # or we will default to the current working directory which was passed to this routine.
    my @siteEntries = xCAT::TableUtils->get_site_attribute("tmpdir");
    my $workDir     = $siteEntries[0];
    if (!$workDir) {
        $workDir = $dir;
    }

    # get rid of spaces and put in underlines.
    $imagename =~ s/\s+/_/g;


    # Create the directory in which we collect the files prior to tarring them.
    my $ttpath = mkdtemp("$workDir/imgexport.$$.XXXXXX");
    $callback->({ data => ["Creating $ttpath..."] }) if $::VERBOSE;
    my $tpath = "$ttpath/$imagename";
    mkdir("$tpath");
    chmod 0755, $tpath;

    #for statelite
    if ($attribs->{osimage}->{provmethod} eq 'statelite') {

        #copy the rootimgdir over
        my $rootimgdir = $attribs->{linuximage}->{rootimgdir};
        if ($rootimgdir) {
            $callback->({ data => ["Packing root image. It will take a while"] });
            system("cd $rootimgdir; find rootimg |cpio -H newc -o | gzip -c - > $tpath/rootimgtree.gz");
            $attribs->{'rootimgtree'} = "$rootimgdir/rootimgtree.gz";
        } else {
            $callback->({ error => ["Couldn't locate the root image directory. "], errorcode => [1] });
            $rc = system("rm -rf $ttpath");
            if ($rc) {
                $callback->({ error => ["Failed to clean up temp space $ttpath"], errorcode => [1] });
            }
            return 0;
        }

        #get litefile table setting for the image
        my $lftab = xCAT::Table->new("litefile", -create => 1);
        if (!$lftab) {
            $callback->({ error => ["Could not open the litefile table."], errorcode => [1] });
            $rc = system("rm -rf $ttpath");
            if ($rc) {
                $callback->({ error => ["Failed to clean up temp space $ttpath"], errorcode => [1] });
            }
            return 0;
        }

        $callback->({ data => ["Getting litefile settings"] });
        my @imageInfo;
        my @imagegroupsattr = ('groups');

        # Check if this image contains osimage.groups attribute.
        # if so, means user wants to use specific directories to this image.
        my $osimagetab = xCAT::Table->new("osimage", -create => 1);
        my $imagegroups = $osimagetab->getAttribs({ imagename => $imagename }, @imagegroupsattr);
        if ($imagegroups and $imagegroups->{groups}) {

            # get the directories with no names
            push @imageInfo, $lftab->getAttribs({ image => '' }, ('file', 'options'));

            # get for the image groups specific directories
            push @imageInfo, $lftab->getAttribs({ image => $imagegroups->{groups} }, ('file', 'options'));

            # get for the image specific directories
            push @imageInfo, $lftab->getAttribs({ image => $imagename }, ('file', 'options'));
        } else {

            # get the directories with no names
            push @imageInfo, $lftab->getAttribs({ image => '' }, ('file', 'options'));

            # get the ALL directories
            push @imageInfo, $lftab->getAttribs({ image => 'ALL' }, ('file', 'options'));

            # get for the image specific directories
            push @imageInfo, $lftab->getAttribs({ image => $imagename }, ('file', 'options'));
        }

        open(FILE, ">$tpath/litefile.csv") or die "Could not open $tpath/litefile.csv";
        foreach (@imageInfo) {
            my $file = $_->{file};
            if (!$file) { next; }
            my $o = $_->{options};
            if (!$o) {
                $o = "tmpfs";
            }
            print FILE "\"$imagename\",\"$file\",\"$o\",,\n";
        }
        close(FILE);
        $attribs->{'litefile'} = "$rootimgdir/litefile.csv";
    }

    #print Dumper($attribs);

    # make manifest.xml file.  So easy!  This is why we like XML.  I didn't like
    # the idea at first though.
    my $xml = new XML::Simple(RootName => 'xcatimage');
    open(FILE, ">$tpath/manifest.xml") or die "Could not open $tpath/manifest.xml";
    print FILE $xml->XMLout($attribs, noattr => 1, xmldecl => '<?xml version="1.0"?>');

    #print $xml->XMLout($attribs, noattr => 1, xmldecl => '<?xml version="1.0">');
    close(FILE);


    # these are the only files we copy in.  (unless you have extras)
    for my $a ("kernel", "ramdisk", "rootimg") {
        my $filenames = $attribs->{$a};
        if ($filenames) {
            my @file_array = split(',', $filenames);
            foreach my $fn (@file_array) {
                $callback->({ data => ["$fn"] });
                if (-r $fn) {
                    system("cp $fn $tpath");
                } else {
                    $callback->({ error => ["Couldn't find file $fn for $imagename. Skip."], errorcode => [1] });
                }
            }
        }
    }

    for my $a ("template", "pkglist", "otherpkglist", "postinstall", "exlist") {
        my $filenames = $attribs->{linuximage}->{$a};
        if ($filenames) {
            my @file_array = split(',', $filenames);
            foreach my $fn (@file_array) {
                $callback->({ data => ["$fn"] });
                if (-r $fn) {
                    system("cp $fn $tpath");
                } else {
                    $callback->({ error => ["Couldn't find file $fn for $imagename. Skip."], errorcode => [1] });
                }
            }
        }
    }

    for my $a ("synclists") {
        my $filenames = $attribs->{osimage}->{$a};
        if ($filenames) {
            my @file_array = split(',', $filenames);
            foreach my $fn (@file_array) {
                $callback->({ data => ["$fn"] });
                if (-r $fn) {
                    system("cp $fn $tpath");
                } else {
                    $callback->({ error => ["Couldn't find file $fn for $imagename. Skip."], errorcode => [1] });
                }
            }
        }
    }

    # Copy kit
    my @kits = keys %{ $attribs->{kit} };
    foreach my $kit (@kits) {

        my $values = $attribs->{kit}->{$kit};
        if ($values->{kitdir}) {
            my $fn = $values->{kitdir};
            $callback->({ data => ["$fn"] });
            if (-r $fn) {
                system("cp -dr $fn $tpath");
            } else {
                $callback->({ error => ["Couldn't find file $fn for $imagename. Skip."], errorcode => [1] });
            }
        }
    }



    # Copy any raw image files. Multiple files can exist (used by s390x)
    if ($attribs->{rawimagefiles}->{files}) {
        foreach my $fromf (@{ $attribs->{rawimagefiles}->{files} }) {
            $rc = system("cp $fromf $tpath");
            if ($rc != 0) {
                $callback->({ error => ["Unable to copy the raw image file $fromf."], errorcode => [1] });
                $rc = system("rm -rf $ttpath");
                if ($rc != 0) {
                    $callback->({ error => ["Unable to remove $ttpath."], errorcode => [1] });
                }
                return 0;
            }
        }
    }

    # extra files get copied in the extra directory.
    if ($attribs->{extra}) {
        mkdir("$tpath/extra");
        chmod 0755, "$tpath/extra";
        foreach (@{ $attribs->{extra} }) {
            my $fromf = $_->{src};
            print " $fromf\n";
            if (-d $fromf) {
                print "fromf is a directory";
                mkpath("$tpath/extra/$fromf");
                `cp -a $fromf/* $tpath/extra/$fromf/`;
            } else {
                `cp $fromf $tpath/extra`;
            }
        }
    }

    # now get right below all this stuff and tar it up.
    chdir($ttpath);
    $callback->({ data => ["Inside $ttpath."] });

    # Determine the name of the bundle that we will create.
    my ($bundleName, $destDir);
    if (defined $dest) {
        ($bundleName, $destDir) = fileparse($dest);
        if ($bundleName eq '') {
            $bundleName = "$imagename.tgz";
        }
    } else {
        $bundleName = "$imagename.tgz";
    }


    # Determine the full file specification of the image bundle.
    my $remoteDest;
    my $tempBundle;
    if (defined $remoteHost) {

        # For a remote host, we need both a local build bundle
        # and the final remote bundle file specification.
        $tempBundle = mktemp("$workDir/imgexport.$$.XXXXXX");
        chomp($tempBundle);
        $dest       = $tempBundle;
        $remoteDest = "$destDir$bundleName";
    }
    else {
        # Local imgexports go to the current working directory
        $dest = "$dir/$bundleName";
    }

    $callback->({ data => ["Compressing $imagename bundle.  Please be patient."] });
    if ($::VERBOSE) {
        $callback->({ data => ["tar czvf $dest . "] });
        $rc = system("tar czvf $dest . ");
    } else {
        $rc = system("tar czf $dest . ");
    }
    $callback->({ data => ["Done!"] });

    if ($rc) {

        # An error occurred during tar to create the image bundle.
        $callback->({ error => ["Failed to compress archive!  (Maybe there was no space left?)"], errorcode => [1] });
        if (-e $dest) {

            # Remove the partially created image bundle.
            $rc = system("rm $dest");
            if ($rc) {
                $callback->({ error => ["Failed to clean up image bundle $dest"], errorcode => [1] });
            }
        }
    } else {

        # The image bundle was created.
        # If remotehost was specified then move the image bundle off xCAT MN to the remote host.
        if (defined $remoteHost) {
            my $remoteFile = $remoteHost . ':' . $remoteDest;

            $callback->({ data => ["Moving the image bundle to the remote system location $remoteFile"] });
            $rc = system("/usr/bin/scp -B $dest $remoteFile");
            if ($rc) {
                $callback->({ error => ["Unable to copy the image bundle $bundleName to the remote host (Maybe passwordless ssh was not setup?)"], errorcode => [1] });
            }
        }
    }

    # Remove the temporary image bundle if it is still around (for remote exports).
    if (-e $tempBundle) {
        $rc = system("rm -f $tempBundle");
        if ($rc) {
            $callback->({ error => ["Failed to clean up the temporary image bundle $tempBundle"], errorcode => [1] });
        }
    }

    # Remove the directory that we used to collect the files prior to creating the tar ball.
    chdir($dir);
    $rc = system("rm -rf $ttpath");
    if ($rc) {
        $callback->({ error => ["Failed to clean up temp space $ttpath"], errorcode => [1] });
    }

    return;
}

#-------------------------------------------------------

=head3   extract_bundle

    Description : Extract the files from the image tar ball.
    Arguments   : Request
                  callback
                  nodes
                  New profile name to use for the image, if specified.
                  Remote host parameter from the command line
    Returns     : None
    Example     : extract_bundle( $request, $callback, $nodes, $new_profile, $remoteHost );

=cut

#-------------------------------------------------------
sub extract_bundle {
    my $request = shift;

    #print Dumper($request);
    my $callback    = shift;
    my $bundle      = shift;
    my $nodes       = shift;
    my $new_profile = shift;
    my $remoteHost  = shift;
    my $nozip = shift;

    my $xml;
    my $data;
    my $datas;
    my $error = 0;
    my $bundleCopy;

    # Determine the current working directory.
    my $dir = $request->{cwd};    #getcwd;
    $dir = $dir->[0];

    #print Dumper($dir);

    # A working directory will hold the temporary image directory.
    # It will also hold the local copy of a remote image bundle.
    # The directory can be defined in the site table and if not
    # defined there then we will default to using the current working
    # directory.
    my @siteEntries = xCAT::TableUtils->get_site_attribute("tmpdir");
    my $workDir     = $siteEntries[0];
    if (!$workDir) {
        $workDir = $dir;
    }

    # If we have a remote file then transfer it to the xCAT MN first.
    if (defined $remoteHost) {

        # Create unique copy the remote bundle in the working directory
        my $remoteFile = "$remoteHost:$bundle";
        $bundleCopy = `/bin/mktemp $workDir/imgimport.$$.XXXXXX`;
        chomp($bundleCopy);
        $callback->({ data => ["Obtaining the image bundle from the remote system $remoteFile"] });
        my $rc = system("/usr/bin/scp -v -B $remoteFile $bundleCopy");
        if ($rc != 0) {
            $callback->({ error => ["Unable to copy the image bundle $bundle from the remote host (Maybe passwordless ssh was not setup?)"], errorcode => [1] });
            $rc = system("rm -rf $bundleCopy");
            if ($rc) {
                $callback->({ error => ["Failed to remove the local copy of the remote image bundle $bundleCopy"], errorcode => [1] });
            }
            return;
        }
        $bundle = $bundleCopy;
    } else {

        # When we are not doing a remote copy, we need to verify the bundle exists and find its exact location
        unless (-r $bundle) {
            $bundle = "$dir/$bundle";
        }

        unless (-r $bundle) {
            $callback->({ error => ["Cannot find $bundle"], errorcode => [1] });
            return;
        }
    }

    if ($::VERBOSE) {
        if ($bundle =~ m/^\//) {

            # Bundle name began with a slash
            $callback->({ data => ["Bundle file is located at $bundle"] });
        } else {
            my $pwd = cwd();
            $callback->({ data => ["Bundle file is located at $pwd/$bundle"] });
        }
    }

    my $tpath = mkdtemp("$workDir/imgimport.$$.XXXXXX");

    $callback->({ data => ["Unbundling image..."] });
    my $rc;
    if ($nozip) {
        if ($::VERBOSE) {
            $callback->({data=>["tar xvf $bundle -C $tpath"]});
            $rc = system("tar xvf $bundle -C $tpath");
        } else {
            $rc = system("tar xf $bundle -C $tpath");
        }
    } else {
        if ($::VERBOSE) {
        $callback->({ data => ["tar zxvf $bundle -C $tpath"] });
        $rc = system("tar zxvf $bundle -C $tpath");
    } else {
        $rc = system("tar zxf $bundle -C $tpath");
    }
    }

    if ($rc) {
        $callback->({ error => ["Failed to extract bundle $bundle"], errorcode => [1] });

        # Remove the files in the untar directory so that we don't attempt to process
        # a partially untarred image bundle.
        system("rm -rf $tpath");
    }

    # get all the files in the tpath.  These should be all the image names.
    my @files = < $tpath/* >;

    # go through each image directory.  Find the XML and put it into the array.  If there are any
    # errors then the whole thing is over and we error and leave.
    foreach my $imgdir (@files) {
        unless (-r "$imgdir/manifest.xml") {
            $callback->({ error => ["Failed to find manifest.xml file in image bundle"], errorcode => [1] });
            last;
        }

        $xml = new XML::Simple;

        # get the data!
        # put it in an eval string so that it
        $data = eval { $xml->XMLin("$imgdir/manifest.xml") };
        if ($@) {
            $callback->({ error => ["invalid manifest.xml file inside the bundle.  Please verify the XML"], errorcode => [1] });

            #my $foo = $@;
            #$foo =~ s/\n//;
            #$callback->({error=>[$foo],errorcode=>[1]});
            #foreach($@){
            #   last;
            #}
            last;
        }

        #print Dumper($data);
        #push @{$datas}, $data;

        #support imgimport osimage exported by xCAT 2.7
        manifest_adapter($data);

        # now we need to import the files...
        unless (verify_manifest($data, $callback)) {
            $error++;
            next;
        }

        # check media first
        unless (check_media($data, $callback)) {
            $error++;
            next;
        }

        #change profile name if needed
        if ($new_profile) {
            $data = change_profile($data, $callback, $new_profile, $imgdir);
        }

        #import manifest.xml into xCAT database
        unless (set_config($data, $callback)) {
            $error++;
            next;
        }

        # now place files in appropriate directories.
        unless (make_files($data, $imgdir, $callback)) {
            $error++;
            next;
        }

        # put postscripts in the postsctipts table
        if ($nodes) {
            unless (set_postscripts($data, $callback, $nodes)) {
                $error++;
                next;
            }
        }

        my $osimage = $data->{osimage}->{imagename};
        $callback->({ data => ["Successfully imported the image $osimage."] });

    }

    # Clean up for this routine.
    # Remove the temp directory used for the exploded bundle
    if (-e $tpath) {
        $rc = system("rm -rf $tpath");
        if ($rc) {
            $callback->({ error => ["Failed to clean up temp space $tpath"], errorcode => [1] });

            # Don't return just yet.  We want the rest of the cleanup to occur.
        }
    }

    # If this was an import from a remote host then remove the directory created for the remote files.
    # We do not want to leave files hanging around that were brought from another system.
    if (-e $bundleCopy) {
        $rc = system("rm -rf $bundleCopy");
        if ($rc) {
            $callback->({ error => ["Failed to remove the local copy of the remote image bundle $bundleCopy"], errorcode => [1] });
        }
    }
}

sub change_profile {
    my $data        = shift;
    my $callback    = shift;
    my $new_profile = shift;
    my $srcdir      = shift;

    my $old_profile = $data->{osimage}->{profile};
    if ($old_profile eq $new_profile) {
        return $data;    #do nothing if old profile is the same as the new one.
    }

    $data->{osimage}->{profile} = $new_profile;
    my $installdir = xCAT::TableUtils->getInstallDir();
    unless ($installdir) {
        $installdir = '/install';
    }
    if ($data->{linuximage}->{rootimgdir}) {
        if (($data->{osimage}->{osarch} eq "s390x") && ($data->{osimage}->{provmethod} =~ /raw|sysclone/)) {
            $data->{linuximage}->{rootimgdir} = "$installdir/$data->{osimage}->{provmethod}/" . $data->{osimage}->{osvers} . "/" . $data->{osimage}->{osarch} . "/$new_profile";
        } else {
            $data->{linuximage}->{rootimgdir} = "$installdir/netboot/" . $data->{osimage}->{osvers} . "/" . $data->{osimage}->{osarch} . "/$new_profile";
        }

        for my $a ("kernel", "ramdisk", "rootimg", "rootimgtree", "litefile") {
            if ($data->{$a}) {
                my $fn = basename($data->{$a});
                $data->{$a} = $data->{linuximage}->{rootimgdir} . "/$fn";
            }
        }
    }

    my $prov = "netboot";
    if ($data->{osimage}->{provmethod} eq "install") { $prov = "install"; }
    my $platform;
    my $os = $data->{osimage}->{osvers};
    if ($os) {
        if    ($os =~ /rh.*/)     { $platform = "rh"; }
        elsif ($os =~ /centos.*/) { $platform = "centos"; }
        elsif ($os =~ /fedora.*/) { $platform = "fedora"; }
        elsif ($os =~ /sles.*/)   { $platform = "sles"; }
        elsif ($os =~ /SL.*/)     { $platform = "SL"; }
        elsif ($os =~ /win/)      { $platform = "windows"; }
        elsif ($os =~ /ubuntu*/)  { $platform = "ubuntu"; }
    }


    for my $a ("template", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist") {
        my $filenames = $data->{linuximage}->{$a};
        if ($a eq "synclists") {
            $filenames = $data->{osimage}->{$a};
        }
        if ($filenames) {
            my @file_array = split(',', $filenames);
            my @new_file_array = ();
            foreach my $old (@file_array) {
                my $oldfn  = basename($old);
                my $olddir = dirname($old);
                my $newfn;
                my $newdir;

                #if source file is from /opt/xcat/share...,
                #then copy it to /install/custom... directory.
                #Otherwise, copy the file in the same directory
                if ($olddir =~ /$::XCATROOT\/share\/xcat/) {
                    $newdir = "$installdir/custom/$prov/$platform";
                } else {
                    $newdir = $olddir;
                }

                #if the file name contains the old profile name,
                #replace it with the new profile name.
                #Otherwise prefix the old file name with the new profile name.
                if ($oldfn =~ /$old_profile/) {
                    $newfn = $oldfn;
                    $newfn =~ s/$old_profile/$new_profile/;
                } else {
                    $newfn = "$new_profile.$oldfn";
                }

                move("$srcdir/$oldfn", "$srcdir/$newfn");
                push(@new_file_array, "$newdir/$newfn");
            }
            if ($a eq "synclists") {
                $data->{osimage}->{$a} = join(',', @new_file_array);
            } else {
                $data->{linuximage}->{$a} = join(',', @new_file_array);
            }
        }
    }

    #change the image name
    my $new_imgname = $data->{osimage}->{osvers} . "-" . $data->{osimage}->{osarch} . "-" . $data->{osimage}->{provmethod} . "-$new_profile";
    $data->{osimage}->{imagename}    = $new_imgname;
    $data->{linuximage}->{imagename} = $new_imgname;

    return $data;
}

# return 1 for true 0 for false.
# need to make sure media is copied before importing image.
sub check_media {
    my $data     = shift;
    my $callback = shift;
    my $rc       = 0;
    unless ($data->{'media'}) {
        $rc = 1;
    } elsif ($data->{media} eq 'required') {
        my $os          = $data->{osimage}->{osvers};
        my $arch        = $data->{osimage}->{osarch};
        my $installroot = xCAT::TableUtils->getInstallDir();
        unless ($installroot) {
            $installroot = '/install';
        }
        unless (-d "$installroot/$os/$arch") {
            $callback->({ error => ["This image requires that you first copy media for $os-$arch"], errorcode => [1] });
        } else {
            $rc = 1;
        }
    }
    return $rc;
}


sub set_postscripts {
    my $data     = shift;
    my $callback = shift;
    my $nodes    = shift;

    $callback->({ data => ["Adding postscripts."] });

    my @good_nodes = noderange($nodes);

    if (@good_nodes > 0) {
        my @missed = nodesmissed();
        if (@missed > 0) {
            $callback->({ warning => [ "The following nodes will be skipped because they are not in the nodelist table.\n  " . join(',', @missed) ], errorcode => 1 });
        }
    } else {
        $callback->({ error => ["The nodes $nodes are not defined in xCAT DB."], errorcode => 1 });
        return 0;
    }

    my $ptab = xCAT::Table->new('postscripts', -create => 1, -autocommit => 0);
    unless ($ptab) {
        $callback->({ error => ["Unable to open table 'postscripts'"], errorcode => 1 });
        return 0;
    }


    # get xcatdefaults settings
    my @a1 = ();
    my @a2 = ();
    (my $attrs1) = $ptab->getAttribs({ node => "xcatdefaults" }, 'postscripts', 'postbootscripts');
    if ($attrs1) {
        if ($attrs1->{postscripts}) {
            @a1 = split(',', $attrs1->{postscripts});
        }
        if ($attrs1->{postbootscripts}) {
            @a2 = split(',', $attrs1->{postbootscripts});
        }
    }

    #remove the script if it is already in xcatdefaults
    my @a3              = ();
    my @a4              = ();
    my $postscripts     = $data->{postscripts};
    my $postbootscripts = $data->{postbootscripts};
    if ($postscripts)     { @a3 = split(',', $postscripts); }
    if ($postbootscripts) { @a4 = split(',', $postbootscripts); }

    my @a30;
    my @a40;
    if (@a1 > 0 && @a3 > 0) {
        foreach my $tmp1 (@a3) {
            if (!grep /^$tmp1$/, @a1) {
                push(@a30, $tmp1);
            }
        }
        $postscripts = join(',', @a30);
    }
    if (@a2 > 0 && @a4 > 0) {
        foreach my $tmp2 (@a4) {
            if (!grep /^$tmp2$/, @a2) {
                push(@a40, $tmp2);
            }
        }
        $postbootscripts = join(',', @a40);
    }

    #now save to the db
    my %keyhash;
    if ($postscripts || $postbootscripts) {
        $keyhash{postscripts}     = $postscripts;
        $keyhash{postbootscripts} = $postbootscripts;
        $ptab->setNodesAttribs(\@good_nodes, \%keyhash);
        $ptab->commit;
    }

    return 1;
}


sub create_symlink {
    my $data        = shift;
    my $callback    = shift;
    my $otherpkgdir = $data->{linuximage}->{otherpkgdir};
    my @kitcomps    = split(',', $data->{osimage}->{kitcomponents});

    my %tabs   = ();
    my @tables = qw(kit kitrepo kitcomponent);
    foreach my $t (@tables) {
        $tabs{$t} = xCAT::Table->new($t, -create => 1, -autocommit => 1);

        if (!exists($tabs{$t})) {
            my %rsp;
            push @{ $rsp{data} }, "Could not open xCAT table $t";
            xCAT::MsgUtils->message("E", \%rsp, $callback);
            return 1;
        }
    }


    if (defined($otherpkgdir)) {

        # Create otherpkgdir if it doesn't exist
        unless (-d "$otherpkgdir") {
            mkpath("$otherpkgdir");
        }

        if ($data and $data->{osimage} and $data->{osimage}->{kitcomponents}) {
            foreach my $kitcomponent (@kitcomps) {
                (my $kitcomptable) = $tabs{kitcomponent}->getAttribs({ kitcompname => $kitcomponent }, 'kitreponame');
                if ($kitcomptable and $kitcomptable->{'kitreponame'}) {

                    # Create symlink if doesn't exist
                    unless (-d "$otherpkgdir/$kitcomptable->{'kitreponame'}") {
                        (my $kitrepotable) = $tabs{kitrepo}->getAttribs({ kitreponame => $kitcomptable->{'kitreponame'} }, 'kitrepodir');
                        if ($kitrepotable and $kitrepotable->{'kitrepodir'}) {
                            system("ln -sf $kitrepotable->{'kitrepodir'} $otherpkgdir/$kitcomptable->{'kitreponame'}");
                        } else {
                            $callback->({ error => ["Cannot open kitrepo table or kitrepodir do not exist"], errorcode => [1] });
                            next;
                        }
                    }
                } else {
                    $callback->({ error => ["Cannot open kitcomponent table or kitreponame do not exist"], errorcode => [1] });
                    next;
                }
            }
        } else {
            $callback->({ warning => ["osimage table or kitcomponent do not exist"], errorcode => [1] });
            return 1;
        }
    }
    return 1;


}

sub set_config {
    my $data     = shift;
    my $callback = shift;
    my $ostab    = xCAT::Table->new('osimage', -create => 1, -autocommit => 0);
    my $linuxtab = xCAT::Table->new('linuximage', -create => 1, -autocommit => 0);
    my $kittab = xCAT::Table->new('kit', -create => 1, -autocommit => 0);
    my $kitrepotab = xCAT::Table->new('kitrepo', -create => 1, -autocommit => 0);
    my $kitcomptab = xCAT::Table->new('kitcomponent', -create => 1, -autocommit => 0);
    my %keyhash;
    my $osimage = $data->{osimage}->{imagename};

    unless ($ostab) {
        $callback->({ error => ["Unable to open table 'osimage'"], errorcode => 1 });
        return 0;
    }

    unless ($linuxtab) {
        $callback->({ error => ["Unable to open table 'linuximage'"], errorcode => 1 });
        return 0;
    }

    unless ($kittab) {
        $callback->({ error => ["Unable to open table 'kit'"], errorcode => 1 });
        return 0;
    }

    unless ($kitrepotab) {
        $callback->({ error => ["Unable to open table 'kitrepo'"], errorcode => 1 });
        return 0;
    }

    unless ($kitcomptab) {
        $callback->({ error => ["Unable to open table 'kitcomponent'"], errorcode => 1 });
        return 0;
    }

    $callback->({ data => ["Adding $osimage"] }) if $::VERBOSE;

    # now we make a quick hash of what we want to put into this
    my $hash_tmp = $data->{osimage};
    foreach my $key (keys %$hash_tmp) {
        $keyhash{$key} = $hash_tmp->{$key};
    }
    $ostab->setAttribs({ imagename => $osimage }, \%keyhash);
    $ostab->commit;

    %keyhash = ();
    my $hash_tmp1 = $data->{linuximage};
    foreach my $key (keys %$hash_tmp1) {
        $keyhash{$key} = $hash_tmp1->{$key};
    }

    $linuxtab->setAttribs({ imagename => $osimage }, \%keyhash);
    $linuxtab->commit;

    my $kit = $data->{kit};
    foreach my $k (keys %$kit) {
        my $kithash = $kit->{$k};
        %keyhash = ();
        foreach my $key (keys %$kithash) {
            $keyhash{$key} = $kithash->{$key};
        }
        $kittab->setAttribs({ kitname => $k }, \%keyhash);
        $kittab->commit;
    }

    my $kitrepo = $data->{kitrepo};
    foreach my $k (keys %$kitrepo) {
        my $kitrepohash = $kitrepo->{$k};
        %keyhash = ();
        foreach my $key (keys %$kitrepohash) {
            $keyhash{$key} = $kitrepohash->{$key};
        }
        $kitrepotab->setAttribs({ kitreponame => $k }, \%keyhash);
        $kitrepotab->commit;
    }

    my $kitcomp = $data->{kitcomp};
    foreach my $k (keys %$kitcomp) {
        my $kitcomphash = $kitcomp->{$k};
        %keyhash = ();
        foreach my $key (keys %$kitcomphash) {
            $keyhash{$key} = $kitcomphash->{$key};
        }
        $kitcomptab->setAttribs({ kitcompname => $k }, \%keyhash);
        $kitcomptab->commit;
    }

    return 1;
}

#an adapter to convert the manifest structure from 2.7 to 2.8
sub manifest_adapter {
    my $data = shift;

    if (exists($data->{osimage}) or exists($data->{linuximage})) {
        return 0;
    }

    my %colstodel;
    foreach my $col (@{ $xCAT::Schema::tabspec{osimage}->{cols} }) {
        if (defined($data->{$col})) {
            $colstodel{$col} = 1;
            $data->{osimage}->{$col} = $data->{$col};
        }
    }


    foreach my $col (@{ $xCAT::Schema::tabspec{linuximage}->{cols} }) {
        if (defined($data->{$col})) {
            $colstodel{$col} = 1;
            $data->{linuximage}->{$col} = $data->{$col};
        }
    }

    foreach my $col (keys %colstodel) {
        delete($data->{$col});
    }

    return 1;
}


sub verify_manifest {
    my $data     = shift;
    my $callback = shift;
    my $errors   = 0;

    # first make sure that the stuff is defined!
    # For certain fields that are used in later construction of directory structure,
    # we trim whitespace which can occur in some versions of the xml processing.
    unless ($data->{osimage}->{imagename}) {
        $callback->({ error => ["The 'imagename' field is not defined in manifest.xml."], errorcode => [1] });
        $errors++;
    }
    $data->{osimage}->{imagename} =~ s/^\s*(\S*)\s*$/$1/;

    unless ($data->{osimage}->{provmethod}) {
        $callback->({ error => ["The 'provmethod' field is not defined in manifest.xml."], errorcode => [1] });
        $errors++;
    }
    $data->{osimage}->{provmethod} =~ s/^\s*(\S*)\s*$/$1/;

    unless ($data->{osimage}->{profile}) {
        $callback->({ error => ["The 'profile' field is not defined in manifest.xml."], errorcode => [1] });
        $errors++;
    }
    $data->{osimage}->{profile} =~ s/^\s*(\S*)\s*$/$1/;

    unless ($data->{osimage}->{osvers}) {
        $callback->({ error => ["The 'osvers' field is not defined in manifest.xml."], errorcode => [1] });
        $errors++;
    }
    $data->{osimage}->{osvers} =~ s/^\s*(\S*)\s*$/$1/;

    unless ($data->{osimage}->{osarch}) {
        $callback->({ error => ["The 'osarch' field is not defined in manifest.xml."], errorcode => [1] });
        $errors++;
    }
    $data->{osimage}->{osarch} =~ s/^\s*(\S*)\s*$/$1/;

    unless ($data->{osimage}->{provmethod} =~ /install|netboot|statelite|sysclone|raw/) {
        $callback->({ error => [ "Importing images with 'provmethod' " . $data->{osimage}->{provmethod} . " is not supported. Hint: install, netboot, statelite, sysclone or raw" ], errorcode => [1] });
        $errors++;
    }
    if (($data->{osimage}->{provmethod} =~ /sysclone/) && ($data->{osimage}->{osarch} !~ /s390x/)) {
        $callback->({ error => [ "Importing images with 'provemethod' " . $data->{osimage}->{provmethod} . " is not supported for osarch '" . $data->{osimage}->{osarch} . "'" ], errorcode => [1] });
        $errors++;
    }
    $data->{osimage}->{provmethod} =~ s/^\s*(\S*)\s*$/$1/;

    # if the install method is used, then we need to have certain files in place.
    if ($data->{osimage}->{provmethod} =~ /install/) {

        # we need to get the template for this one!
        unless ($data->{linuximage}->{template}) {
            $callback->({ error => ["The 'osarch' field is not defined in manifest.xml."], errorcode => [1] });
            $errors++;
        }

        #$attrs->{media} = "required"; (need to do something to verify media!

    } elsif ($data->{osimage}->{osvers} =~ /esx/) {
        $callback->({ info => ['this is an esx image'] });

        # do nothing for ESX
        1;
    } elsif ($data->{osimage}->{provmethod} =~ /netboot/) {
        if ($data->{osimage}->{osarch} =~ /s390x/) {
            if (!$data->{rawimagefiles}) {
                $callback->({ error => ["The 'rawimagefiles' section is not defined in manifest.xml."], errorcode => [1] });
                $errors++;
            } elsif (!$data->{rawimagefiles}->{files}) {
                $callback->({ error => ["'files' were not specified in the 'rawimagefiles' section of manifest.xml."], errorcode => [1] });
                $errors++;
            }
        } else {
            unless ($data->{ramdisk}) {
                $callback->({ error => ["The 'ramdisk' field is not defined in manifest.xml."], errorcode => [1] });
                $errors++;
            }
            unless ($data->{kernel}) {
                $callback->({ error => ["The 'kernel' field is not defined in manifest.xml."], errorcode => [1] });
                $errors++;
            }
            unless ($data->{rootimg}) {
                $callback->({ error => ["The 'rootimg' field is not defined in manifest.xml."], errorcode => [1] });
                $errors++;
            }
        }

    } elsif ($data->{osimage}->{provmethod} =~ /statelite/) {
        unless ($data->{kernel}) {
            $callback->({ error => ["The 'kernel' field is not defined in manifest.xml."], errorcode => [1] });
            $errors++;
        }
        unless ($data->{ramdisk}) {
            $callback->({ error => ["The 'ramdisk' field is not defined in manifest.xml."], errorcode => [1] });
            $errors++;
        }
        unless ($data->{'rootimgtree'}) {
            $callback->({ error => ["The 'rootimgtree' field is not defined in manifest.xml."], errorcode => [1] });
            $errors++;
        }

    } elsif ($data->{osimage}->{provmethod} =~ /sysclone/) {
        if ($data->{osimage}->{osarch} =~ /s390x/) {
            if (!$data->{rawimagefiles}) {
                $callback->({ error => ["The 'rawimagefiles' section is not defined in manifest.xml."], errorcode => [1] });
                $errors++;
            } elsif (!$data->{rawimagefiles}->{files}) {
                $callback->({ error => ["'files' were not specified in the 'rawimagefiles' section of manifest.xml."], errorcode => [1] });
                $errors++;
            }
        }
    }

    if ($errors) {

        # we had problems, error and exit.
        return 0;
    }

    # returning 1 means everything went good!
    return 1;
}

sub make_files {
    my $data        = shift;
    my $imgdir      = shift;
    my $callback    = shift;
    my $os          = $data->{osimage}->{osvers};
    my $arch        = $data->{osimage}->{osarch};
    my $profile     = $data->{osimage}->{profile};
    my $installroot = xCAT::TableUtils->getInstallDir();
    unless ($installroot) {
        $installroot = '/install';
    }

    # you'll get a hash like this for install:
    #$VAR1 = {
    #          osimage=> {
    #              'imagename' => 'Default_Stateful',
    #              'provmethod' => 'install',
    #              'profile' => 'all',
    #              'osarch' => 'x86_64',
    #              'osvers' => 'centos5.4'
    #              'synclists' => '/opt/xcat/share/xcat/install/centos/all.othetpkgs.synclist',
    #          }
    #          linuxiage=> {
    #              'template' => '/opt/xcat/share/xcat/install/centos/all.tmpl',
    #              'pkglist' => '/opt/xcat/share/xcat/install/centos/all.pkglist',
    #              'otherpkglist' => '/opt/xcat/share/xcat/install/centos/all.othetpkgs.pkglist',
    #              'imagename' => 'Default_Stateful',
    #          }
    #          'media' => 'required',
    #        };

    # data will look something like this for netboot:
    #$VAR1 = {
    #          'ramdisk' => '/install/netboot/centos5.4/x86_64/compute/initrd-stateless.gz',
    #          'rootimg' => '/install/netboot/centos5.4/x86_64/compute/rootimg.gz'
    #          'kernel' => '/install/netboot/centos5.4/x86_64/compute/kernel',
    #          osimage=> {
    #              'imagename' => 'Default_Stateless_1265981465',
    #              'osvers' => 'centos5.4',
    #              'osarch' => 'x86_64',
    #              'provmethod' => 'netboot',
    #              'profile' => 'compute',
    #              'synclists' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.synclist',
    #          }
    #          linuximage=> {
    #              'imagename' => 'Default_Stateless_1265981465',
    #              'pkglist' => '/opt/xcat/share/xcat/install/centos/compute.pkglist',
    #              'otherpkglist' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.pkglist',
    #              'exlist' => '/opt/xcat/share/xcat/install/centos/compute.exlist',
    #              'postinstall' => '/opt/xcat/share/xcat/install/centos/compute.postinstall',
    #          }
    #          'extra' => [
    #                     {
    #                       'dest' => '/install/custom/netboot/centos',
    #                       'src' => '/opt/xcat/share/xcat/netboot/centos/compute.centos5.4.pkglist'
    #                     },
    #                     {
    #                       'dest' => '/install/custom/netboot/centos',
    #                       'src' => '/opt/xcat/share/xcat/netboot/centos/compute.exlist'
    #                     }
    #                   ],
    #        };
    # data will look something like this for statelite:
    #$VAR1 = {
    #          'ramdisk' => '/install/netboot/centos5.4/x86_64/compute/initrd-statelite.gz',
    #          'kernel' => '/install/netboot/centos5.4/x86_64/compute/kernel',
    #          'rootimgtree' => '/install/netboot/centos5.4/x86_64/compute/rootimg/rootimgtree.gz'
    #          osimage=> {
    #              'osvers' => 'centos5.4',
    #              'osarch' => 'x86_64',
    #              'imagename' => 'Default_Stateless_1265981465',
    #              'provmethod' => 'statelite',
    #              'profile' => 'compute',
    #               'synclists' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.synclist',
    #          }
    #          linuximage=> {
    #              'imagename' => 'Default_Stateless_1265981465',
    #              'pkglist' => '/opt/xcat/share/xcat/install/centos/compute.pkglist',
    #              'otherpkglist' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.pkglist',

    #              'exlist' => '/opt/xcat/share/xcat/install/centos/compute.exlist',
    #              'postinstall' => '/opt/xcat/share/xcat/install/centos/compute.postinstall',
    #          }
    #          'extra' => [
    #                     {
    #                       'dest' => '/install/custom/netboot/centos',
    #                       'src' => '/opt/xcat/share/xcat/netboot/centos/compute.centos5.4.pkglist'
    #                     },
    #                     {
    #                       'dest' => '/install/custom/netboot/centos',
    #                       'src' => '/opt/xcat/share/xcat/netboot/centos/compute.exlist'
    #                     }
    #                   ],
    #        };

    for my $a ("kernel", "ramdisk", "rootimg", "rootimgtree", "litefile") {
        my $filenames = $data->{$a};
        if ($filenames) {
            my @file_array = split(',', $filenames);
            foreach my $fn (@file_array) {
                $callback->({ data => ["$fn"] });
                my $basename = basename($fn);
                my $dirname  = dirname($fn);
                if (!-r $dirname) {
                    mkpath("$dirname", { verbose => 1, mode => 0755 });
                }
                if (-r $fn) {
                    $callback->({ data => ["  Moving old $fn to $fn.ORIG."] });
                    move("$fn", "$fn.ORIG");
                }
                move("$imgdir/$basename", $fn);
            }
        }
    }

    for my $a ("template", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist") {
        my $filenames;
        if ($a eq "synclists") {
            $filenames = $data->{osimage}->{$a};
        } else {
            $filenames = $data->{linuximage}->{$a};
        }
        if ($filenames) {
            my @file_array = split(',', $filenames);
            foreach my $fn (@file_array) {
                $callback->({ data => ["$fn"] });
                my $basename = basename($fn);
                my $dirname  = dirname($fn);
                if (!-r $dirname) {
                    mkpath("$dirname", { verbose => 1, mode => 0755 });
                }
                if (-r $fn) {
                    $callback->({ data => ["  Moving old $fn to $fn.ORIG."] });
                    move("$fn", "$fn.ORIG");
                }
                move("$imgdir/$basename", $fn);
            }
        }
    }

    # unpack kit
    my $k = $data->{kit};
    foreach my $kit (keys %$k) {
        my $fn = $k->{$kit}->{kitdir};
        if ($fn) {
            my $dirname = dirname($fn);
            if (!-r $dirname) {
                mkpath("$dirname", { verbose => 1, mode => 0755 });
            }

            if (-r "$dirname/$kit") {
                $callback->({ data => ["  Moving old $fn to $fn.ORIG."] });
                move("$dirname/$kit", "$dirname/$kit.ORIG");

            }
            move("$imgdir/$kit", "$dirname/$kit");

            #copy postscripts from kit dir to postscripts dir;
            copyPostscripts($dirname, $kit, $installroot, $callback);

            #copy plugin from kit to xCAT_plugin
            movePlugin($dirname, $kit, $callback);
        }
    }
    if ($hasplugin) {

        # Issue xcatd reload to load the new plugins
        system("/etc/init.d/xcatd restart");
        $hasplugin = 0;
    }


    #unpack the rootimgtree.gz for statelite
    my $fn = $data->{'rootimgtree'};
    if ($fn) {
        if (-r $fn) {
            my $basename = basename($fn);
            my $dirname  = dirname($fn);

            #print "dirname=$dirname, basename=$basename\n";
            $callback->({ data => ["Extracting rootimgtree.gz. It will take a while."] });
            system("mkdir -p $dirname; cd $dirname; zcat $basename |cpio -idum; rm $basename");
        }
    }

    if ($data->{extra}) {

        # have to copy extras
        print "copying extras\n" if $::VERBOSE;

        #if its just a hash then there is only one entry.
        if (ref($data->{extra}) eq 'HASH') {
            my $ex = $data->{extra};

            #my $f = basename($ex->{src});
            my $ff   = $ex->{src};
            my $dest = $ex->{dest};
            unless (moveExtra($callback, $ff, $dest, $imgdir)) {
                return 0;
            }

            # if its an array go through each item.
        } else {
            foreach (@{ $data->{extra} }) {

                #my $f = basename($_->{src});
                my $ff   = $_->{src};
                my $dest = $_->{dest};
                unless (moveExtra($callback, $ff, $dest, $imgdir)) {
                    return 0;
                }
            }
        }
    }


    #litefile table for statelite
    if ($data->{osimage}->{provmethod} eq 'statelite') {
        $callback->({ data => ["Updating the litefile table."] });
        my $fn = $data->{litefile};
        if (!$fn) {
            $callback->({ error => ["Could not find litefile.csv."], errorcode => [1] });
            return 1;
        } elsif (!-r $fn) {
            $callback->({ error => ["Could not find $fn."], errorcode => [1] });
            return 1;
        }

        my $lftab = xCAT::Table->new("litefile", -create => 1);
        if (!$lftab) {
            $callback->({ error => ["Could not open the litefile table."], errorcode => [1] });
            return 0;
        }

        #get current litefile table entries
        my @entries = $lftab->getAllAttribsWhere("\"image\" = 'ALL'", 'file', 'options');

        #get the entries for image
        open(FILE, "$fn") or die "Could not open $fn.";
        foreach my $line (<FILE>) {
            chomp($line);
            print "$line\n";
            my @tmp = split('"', $line);
            my %keyhash;
            my %updates;
            my $filename = $tmp[3];
            my $options  = $tmp[5];

            # If there is an entry with image as ALL for the same file,
            # then no need to add the file name in the table
            my $skip = 0;
            foreach my $entry (@entries) {
                if (($filename eq $entry->{'file'}) && ($options eq $entry->{'options'})) {
                    $skip = 1;
                    last;
                }
            }

            if ($skip == 0) {
                $keyhash{image}   = $data->{osimage}->{imagename};
                $keyhash{file}    = $filename;
                $updates{options} = $options;
                $lftab->setAttribs(\%keyhash, \%updates);
            }
        }
        close(FILE);
        $lftab->commit;

        $callback->({ data => ["The litetree and statelite tables are untouched. You can update them if needed."] });
    }

    # For s390x copy all image files from the root bundle directory to the repository location
    if (($data->{osimage}->{osarch} =~ /s390x/) && ($data->{osimage}->{provmethod} =~ /raw|netboot|sysclone/)) {
        my $reposImgDir = "$installroot/$data->{osimage}->{provmethod}/$data->{osimage}->{osvers}/$data->{osimage}->{osarch}/$data->{osimage}->{profile}";
        mkpath($reposImgDir);

        if ($data->{rawimagefiles}) {
            $callback->({ data => ["Copying image files to $reposImgDir"] });

            my $rif   = $data->{rawimagefiles};
            my $files = $rif->{files};
            if (ref($files) eq 'ARRAY') {
                foreach (@$files) {
                    my $old_file = basename($_);
                    my ($suffix) = $old_file =~ /(\.[^.]+)$/;
                    if ($suffix ne ".img") {
                        $suffix = ".img";
                    } else {
                        $suffix = "";
                    }

                    $callback->({ data => ["Moving $old_file to $reposImgDir as $old_file$suffix"] });
                    my $rc = move("$imgdir/$old_file", "$reposImgDir/$old_file$suffix");
                    if ($rc == 0) {
                        $callback->({ error => ["Could not move $old_file to $reposImgDir: $!\n"], errorcode => [1] });
                        return 0;
                    }
                }
            } else {
                my $old_file = basename($files);
                my ($suffix) = $old_file =~ /(\.[^.]+)$/;
                if ($suffix ne ".img") {
                    $suffix = ".img";
                } else {
                    $suffix = "";
                }

                $callback->({ data => ["Moving $old_file to $reposImgDir as $old_file$suffix"] });
                my $rc = move("$imgdir/$old_file", "$reposImgDir/$old_file$suffix");
                if ($rc == 0) {
                    $callback->({ error => ["Could not move $old_file to $reposImgDir: $!\n"], errorcode => [1] });
                    return 0;
                }
            }
        }
    }

    # return 1 meant everything was successful!
    return 1;
}

sub copyPostscripts {
    my $dirname    = shift;
    my $kit        = shift;
    my $installdir = shift;
    my $callback   = shift;
    my $fenv       = '\.env$';
    my $fexlist    = '\.exlist$';

    if (-d "$dirname/$kit/other_files/") {
        opendir(DIRP, "$dirname/$kit/other_files/");
        foreach my $f (readdir(DIRP)) {
            if (($f =~ m/^\./) || ($f =~ /$fexlist/i) || ($f =~ m/$fenv/i)) {
                next;
            } else {
                print "$f\n";
                chmod(0755, "$dirname/$kit/other_files/$f");
                system("cp -rfv $dirname/$kit/other_files/$f $installdir/postscripts/");
            }
        }

        closedir(DIRP);
    }
}

sub movePlugin {

    my $dirname  = shift;
    my $kit      = shift;
    my $callback = shift;

    if (-d "$dirname/$kit/plugins/") {
        chmod(644, "$dirname/$kit/plugins/*");
        opendir(DIR, "$dirname/$kit/plugins/");
        if (grep { ($_ ne '.') && ($_ ne '..') } readdir(DIR)) {
            system("cp -rfv $dirname/$kit/plugins/* $::XCATROOT/lib/perl/xCAT_plugin/");
            $hasplugin = 1;
        }
        closedir(DIR);
    }

}

sub moveExtra {
    my $callback = shift;
    my $ff       = shift;
    my $dest     = shift;
    my $imgdir   = shift;
    my $f        = basename($ff);

    if (-d "$imgdir/extra/$ff") {

        #print "This is a directory\n";
        # this extra file is a directory, so we are moving the directory over.
        $callback->({ data => ["$dest"] });
        unless (-d $dest) {
            unless (mkpath($dest)) {
                $callback->({ error => ["Failed to create $dest"], errorcode => 1 });
                return 0;
            }
        }

        # this could cause some problems.  This is one of the reasons we may not want to
        # allow copying of directories.
        `cp -a -f $imgdir/extra/$ff/* $dest`;
        if ($?) {
            $callback->({ error => ["Failed to cp -a $imgdir/extra/$ff/* to $dest"], errorcode => 1 });
            return 0;
        }
    } else {

        #print "This is a file\n";
        # this extra file is a file and we can just copy to the destination.
        $callback->({ data => ["$dest/$f"] });
        if (-r "$dest/$f") {
            $callback->({ data => ["  Moving old $dest/$f to $dest/$f.ORIG."] });
            move("$dest/$f", "$dest/$f.ORIG");
        }
        `cp $imgdir/extra/$f $dest`;
        if ($?) {
            $callback->({ error => ["Failed to copy $imgdir/extra/$f to $dest"], errorcode => 1 });
            return 0;
        }
    }
    return 1;
}

