#!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html package xCAT::BuildKitUtils; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } # if AIX - make sure we include perl 5.8.2 in INC path. # Needed to find perl dependencies shipped in deps tarball. if ($^O =~ /^aix/i) { unshift(@INC, qw(/usr/opt/perl5/lib/5.8.2/aix-thread-multi /usr/opt/perl5/lib/5.8.2 /usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi /usr/opt/perl5/lib/site_perl/5.8.2)); } use lib "$::XCATROOT/lib/perl"; use POSIX qw(ceil); use File::Path; use Socket; use strict; use Symbol; my $sha1support; if ( -f "/etc/debian_version" ) { $sha1support = eval { require Digest::SHA; 1; }; } else { $sha1support = eval { require Digest::SHA1; 1; }; } use IPC::Open3; use IO::Select; use warnings "all"; our @ISA = qw(Exporter); #------------------------------------------------------------------------------- =head1 xCAT::BuildKitUtils =head2 Package Description This program module file, is a set of utilities used by xCAT buildkit command =cut #------------------------------------------------------------- #-------------------------------------------------------------------------- =head3 get_latest_version Find the latest version in a list of rpms with the same basename Arguments: - the repo location - a list of rpms with the same basename Returns: - name of rpm - undef Example: my $new_d = xCAT::BuildKitUtils->get_latest_version($repodir, \@rpmlist); Comments: =cut #-------------------------------------------------------------------------- sub get_latest_version { my ($class, $repodir, $rpms) = @_; my @rpmlist = @$rpms; my %localversions_hash = (); my %file_name_hash = (); my $i = 0; foreach my $rpm (@rpmlist) { # include path my $fullrpmpath = "$repodir/$rpm*"; # get the basename, version, and release for this rpm my $rcmd = "rpm -qp --queryformat '%{N} %{V} %{R}\n' $repodir/$rpm"; my $out = `$rcmd`; my ($rpkg, $VERSION, $RELEASE) = split(' ', $out); chomp $VERSION; chomp $RELEASE; $localversions_hash{$i}{'VERSION'} = $VERSION; $localversions_hash{$i}{'RELEASE'} = $RELEASE; $file_name_hash{$VERSION}{$RELEASE} = $rpm; $i++; } if ($i == 0) { print "error\n"; return undef; } my $versionout = ""; my $releaseout = ""; $i = 0; foreach my $k (keys %localversions_hash) { if ($i == 0) { $versionout = $localversions_hash{$k}{'VERSION'}; $releaseout = $localversions_hash{$k}{'RELEASE'}; } # if this is a newer version/release then set them if ( xCAT::BuildKitUtils->testVersion($localversions_hash{$k}{'VERSION'}, ">", $versionout, $localversions_hash{$k}{'RELEASE'}, $releaseout) ) { $versionout = $localversions_hash{$k}{'VERSION'}; $releaseout = $localversions_hash{$k}{'RELEASE'}; } $i++; } if ($i == 0) { print "Error: Could not determine the latest version for the following list of rpms: @rpmlist\n"; return undef; } else { return ($file_name_hash{$versionout}{$releaseout}); } } #-------------------------------------------------------------------------- =head3 get_latest_version_deb Find the latest version in a list of debs with the same basename Arguments: - the repo location - a list of debs with the same basename Returns: - name of deb - undef Example: my $new_d = xCAT::BuildKitUtils->get_latest_version($repodir, \@rpmlist); Comments: =cut #-------------------------------------------------------------------------- sub get_latest_version_deb { my ($class, $repodir, $debs) = @_; my @deblist = @$debs; my %localversions_hash = (); my $file_name; my %founddeb; my $latest; my $i = 0; foreach my $deb (@deblist) { # include path my $fulldebpath = "$repodir/$deb*"; chomp $deb; # get the basename, version, and release for this deb print "dpkg -I $repodir/$deb |grep Package|awk '{print \$2}'"; my $basenamedeb = `dpkg -I $repodir/$deb |grep Package|awk '{print \$2}'`; chomp $basenamedeb; my $versiondeb = `dpkg -I $repodir/$deb |grep Version|awk '{print \$2}'`; chomp $versiondeb; $founddeb{$basenamedeb}{$deb}{version}=$versiondeb; $i++; } if ($i == 0) { print "error\n"; return undef; } foreach my $r (keys %founddeb ) { # if more than one with same basename then find the latest my $latestmatch=""; foreach my $fdeb (keys %{$founddeb{$r}} ) { # if we already found a match in some other dir if ($latestmatch) { # then we need to figure out which is the newest # if the $fdeb is newer than use iti if ( ! xCAT::BuildKitUtils->testVersion_deb($founddeb{$r}{$fdeb}{version}, 'gt', $founddeb{$r}{$latestmatch}{version}) ) { $latestmatch = $fdeb; } } else { $latestmatch = $fdeb; } } $latest=$latestmatch; } if ($i == 0) { print "Error: Could not determine the latest version for the following list of debs: @deblist\n"; return undef; } else { return ($latest); } } #-------------------------------------------------------------------------- =head3 find_latest_pkg Find the latest rpm package give the rpm name and a list of possible package locations. Arguments: - a list of package directories - the name of the rpm Returns: - \@foundrpmlist - undef Example: my $newrpm = xCAT::BuildKitUtils->find_latest_pkg(\@pkgdirs, $rpmname); Comments: =cut #-------------------------------------------------------------------------- sub find_latest_pkg { my ($class, $pkgdirs, $rpmname) = @_; my @pkgdirlist = @$pkgdirs; my @rpms; my %foundrpm; # need to check each pkgdir for the rpm(s) # - if more than one match need to pick latest # find all the matches in all the directories foreach my $rpmdir (@pkgdirlist) { my $ffile = $rpmdir."/".$rpmname; if ( system("/bin/ls $ffile > /dev/null 2>&1") ){ # if not then skip to next dir next; } else { # if so then get the details and add it to the %foundrpm hash my $cmd = "/bin/ls $ffile 2>/dev/null"; my $output = `$cmd`; my @rpmlist = split(/\n/, $output); if ( scalar(@rpmlist) == 0) { # no rpms to check next; } foreach my $r (@rpmlist) { my $rcmd = "rpm -qp --queryformat '%{N} %{V} %{R}\n' $r*"; my $out = `$rcmd`; my ($name, $fromV, $fromR) = split(' ', $out); chomp $fromV; chomp $fromR; # ex. {ppe_rte_man}{/tmp/rpm1/ppe_rte_man-1.3.0.5-s005a.x86_64.rpm}{version}=$fromV; $foundrpm{$name}{$r}{version}=$fromV; $foundrpm{$name}{$r}{release}=$fromR; #print "name=$name, full=$r, verson= $fromV, release=$fromR\n"; } } } # for each unique rpm basename foreach my $r (keys %foundrpm ) { # if more than one with same basename then find the latest my $latestmatch=""; foreach my $frpm (keys %{$foundrpm{$r}} ) { # if we already found a match in some other dir if ($latestmatch) { # then we need to figure out which is the newest # if the $frpm is newer than use it if ( xCAT::BuildKitUtils->testVersion($foundrpm{$r}{$frpm}{version}, ">", $foundrpm{$r}{$latestmatch}{version}, $foundrpm{$r}{$frpm}{release}, $foundrpm{$r}{$latestmatch}{release}) ) { $latestmatch = $frpm; } } else { $latestmatch = $frpm; } } push(@rpms, $latestmatch); } if (scalar(@rpms)) { return \@rpms; } else { return undef; } } #-------------------------------------------------------------------------- =head3 find_latest_pkg_deb Find the latest deb package give the deb name and a list of possible package locations. Arguments: - a list of package directories - the name of the deb Returns: - \@founddeblist - undef Example: my $newrpm = xCAT::BuildKitUtils->find_latest_pkg_deb(\@pkgdirs, $debname); Comments: =cut #-------------------------------------------------------------------------- sub find_latest_pkg_deb { my ($class, $pkgdirs, $debname) = @_; my @pkgdirlist = @$pkgdirs; my @debs; my %founddeb; # need to check each pkgdir for the deb(s) # - if more than one match need to pick latest # find all the matches in all the directories foreach my $debdir (@pkgdirlist) { my $ffile = $debdir."/".$debname; if ( system("/bin/ls $ffile > /dev/null 2>&1") ){ # if not then skip to next dir next; } else { # if so then get the details and add it to the %founddeb hash my $cmd = "/bin/ls $ffile 2>/dev/null"; my $output = `$cmd`; my @deblist = split(/\n/, $output); if ( scalar(@deblist) == 0) { next; } foreach my $r (@deblist) { my $basename = `dpkg -I $r* |grep Package|awk '{print \$2}'|head -1`; chomp $basename; my $version = `dpkg -I $r* |grep Version|awk '{print \$2}'|head -1`; chomp $version; $founddeb{$basename}{$r}{version}=$version; } } } # for each unique deb basename foreach my $r (keys %founddeb ) { # if more than one with same basename then find the latest my $latestmatch=""; foreach my $fdeb (keys %{$founddeb{$r}} ) { # if we already found a match in some other dir if ($latestmatch) { # then we need to figure out which is the newest # if the $fdeb is newer than use it if ( ! xCAT::BuildKitUtils->testVersion_deb($founddeb{$r}{$fdeb}{version}, 'gt', $founddeb{$r}{$latestmatch}{version}) ) { $latestmatch = $fdeb; } } else { $latestmatch = $fdeb; } } push(@debs, $latestmatch); } if (scalar(@debs)) { return \@debs; } else { return undef; } } #------------------------------------------------------------------------------ =head3 testVersion_deb Compare version1 and version2 according to the operator and return 1 0 or 0. Arguments: $version1 $operator $version2 Returns: 1 or 0 Example: Comments: The return value is generated with the Require query =cut #----------------------------------------------------------------------------- sub testVersion_deb { my ($class, $version1, $operator, $version2) = @_; if ($::VERBOSE) { print "dpkg --compare-versions $version1 $operator $version2 \n"; } my $result =`dpkg --compare-versions $version1 $operator $version2`; return $result; } #------------------------------------------------------------------------------ =head3 testVersion Compare version1 and version2 according to the operator and return True or False. Arguments: $version1 $operator $version2 $release1 $release2 Returns: True or False Example: Comments: The return value is generated with the Require query of the rpm command. =cut #----------------------------------------------------------------------------- sub testVersion { my ($class, $version1, $operator, $version2, $release1, $release2) = @_; my @a1 = split(/\./, $version1); my @a2 = split(/\./, $version2); my $len = (scalar(@a1) > scalar(@a2) ? scalar(@a1) : scalar(@a2)); $#a1 = $len - 1; # make the arrays the same length before appending release $#a2 = $len - 1; push @a1, split(/\./, $release1); push @a2, split(/\./, $release2); $len = (scalar(@a1) > scalar(@a2) ? scalar(@a1) : scalar(@a2)); my $num1 = ''; my $num2 = ''; for (my $i = 0 ; $i < $len ; $i++) { # remove any non-numbers on the end if(defined($a1[$i])&&defined($a2[$i])) { my ($d1) = $a1[$i] =~ /^(\d*)/; my ($d2) = $a2[$i] =~ /^(\d*)/; my $diff = length($d1) - length($d2); if ($diff > 0) # pad d2 { $num1 .= $d1; $num2 .= ('0' x $diff) . $d2; } elsif ($diff < 0) # pad d1 { $num1 .= ('0' x abs($diff)) . $d1; $num2 .= $d2; } else # they are the same length { $num1 .= $d1; $num2 .= $d2; } } } # Remove the leading 0s or perl will interpret the numbers as octal $num1 =~ s/^0+//; $num2 =~ s/^0+//; # be sure that $num1 is not a "". if (length($num1) == 0) { $num1 = 0; } if (length($num2) == 0) { $num2 = 0; } if ($operator eq '=') { $operator = '=='; } my $bool = eval "$num1 $operator $num2"; return $bool; } #------------------------------------------------------------------------------- =head3 isAIX returns 1 if localHost is AIX Arguments: none Returns: 1 - localHost is AIX 0 - localHost is some other platform Globals: none Error: none Example: if (xCAT::BuildKitUtils->isAIX()) { blah; } Comments: none =cut #------------------------------------------------------------------------------- sub isAIX { if ($^O =~ /^aix/i) { return 1; } else { return 0; } } #------------------------------------------------------------------------------- =head3 get_OS_VRMF Arguments: none Returns: v.r.m.f - if success undef - if error Example: my $osversion = xCAT::BuildKitUtils->get_OS_VRMF(); Comments: Only implemented for AIX for now =cut #------------------------------------------------------------------------------- sub get_OS_VRMF { my $version; if (xCAT::BuildKitUtils->isAIX()) { my $cmd = "/usr/bin/lslpp -cLq bos.rte"; my $output = `$cmd`; chomp($output); # The third field in the lslpp output is the VRMF $version = (split(/:/, $output))[2]; # not sure if the field would ever contain more than 4 parts? my ($v1, $v2, $v3, $v4, $rest) = split(/\./, $version); $version = join(".", $v1, $v2, $v3, $v4); } return (length($version) ? $version : undef); } #------------------------------------------------------------------------------- =head3 isLinux returns 1 if localHost is Linux Arguments: none Returns: 1 - localHost is Linux 0 - localHost is some other platform Globals: none Error: none Example: if (xCAT::BuildKitUtils->isLinux()) { blah; } Comments: none =cut #------------------------------------------------------------------------------- sub isLinux { if ($^O =~ /^linux/i) { return 1; } else { return 0; } } #-------------------------------------------------------------------------------- =head3 CreateRandomName Create a random file name. Arguments: Prefix of name Returns: Prefix with 8 random letters appended Error: none Example: $file = xCAT::BuildKitUtils->CreateRandomName($namePrefix); Comments: None =cut #------------------------------------------------------------------------------- sub CreateRandomName { my ($class, $name) = @_; my $nI; for ($nI = 0 ; $nI < 8 ; $nI++) { my $char = ('a' .. 'z', 'A' .. 'Z')[int(rand(52)) + 1]; $name .= $char; } $name; } #----------------------------------------------------------------------- =head3 close_delete_file. Arguments: file handle,filename Returns: none Globals: none Error: undef Example: xCAT::BuildKitUtils->close_delete_file($file_handle, $file_name); Comments: none =cut #------------------------------------------------------------------------ sub close_delete_file { my ($class, $file_handle, $file_name) = @_; close $file_handle; unlink($file_name); } #------------------------------------------------------------------------------- =head3 CheckVersion Checks the two versions numbers to see which one is greater. Arguments: ver_a the version number in format of d.d.d.d... ver_b the version number in format of d.d.d.d... Returns: 1 if ver_a is greater than ver_b 0 if ver_a is eaqual to ver_b -1 if ver_a is smaller than ver_b =cut #------------------------------------------------------------------------------- sub CheckVersion { my $ver_a = shift; if ($ver_a =~ /xCAT::BuildKitUtils/) { $ver_a = shift; } my $ver_b = shift; my @a = split(/\./, $ver_a); my @b = split(/\./, $ver_b); my $len_a = @a; my $len_b = @b; my $index = 0; my $max_index = ($len_a > $len_b) ? $len_a : $len_b; for ($index = 0 ; $index <= $max_index ; $index++) { my $val_a = ($len_a < $index) ? 0 : $a[$index]; my $val_b = ($len_b < $index) ? 0 : $b[$index]; if ($val_a > $val_b) { return 1; } if ($val_a < $val_b) { return -1; } } return 0; } #------------------------------------------------------------------------------- =head3 osver Returns the os and version of the System you are running on Arguments: none Returns: 0 - ok Globals: none Error: 1 error Example: my $os=(xCAT::BuildKitUtils->osver{ ...} Comments: none =cut #------------------------------------------------------------------------------- sub osver { my $osver = "unknown"; my $os = ''; my $ver = ''; my $line = ''; my @lines; my $relfile; if (-f "/etc/redhat-release") { open($relfile,"<","/etc/redhat-release"); $line = <$relfile>; close($relfile); chomp($line); $os = "rh"; $ver=$line; # $ver=~ s/\.//; $ver =~ s/[^0-9]*([0-9.]+).*/$1/; if ($line =~ /AS/) { $os = 'rhas' } elsif ($line =~ /ES/) { $os = 'rhes' } elsif ($line =~ /WS/) { $os = 'rhws' } elsif ($line =~ /Server/) { $os = 'rhels' } elsif ($line =~ /Client/) { $os = 'rhel' } elsif (-f "/etc/fedora-release") { $os = 'rhfc' } } elsif (-f "/etc/SuSE-release") { open($relfile,"<","/etc/SuSE-release"); @lines = <$relfile>; close($relfile); chomp(@lines); if (grep /SLES|Enterprise Server/, @lines) { $os = "sles" } if (grep /SLEC/, @lines) { $os = "slec" } $ver = $lines[0]; $ver =~ s/\.//; $ver =~ s/[^0-9]*([0-9]+).*/$1/; my $minorver; if (grep /PATCHLEVEL/, $lines[2]) { $minorver = $lines[2]; $minorver =~ s/PATCHLEVEL[^0-9]*([0-9]+).*/$1/; } $ver = $ver . ".$minorver" if ( $minorver ); } elsif (-f "/etc/UnitedLinux-release") { $os = "ul"; open($relfile,"<","/etc/UnitedLinux-release"); $ver = <$relfile>; close($relfile); $ver =~ tr/\.//; $ver =~ s/[^0-9]*([0-9]+).*/$1/; } elsif (-f "/etc/lsb-release") # Possibly Ubuntu { if (open($relfile,"<","/etc/lsb-release")) { my @text = <$relfile>; close($relfile); chomp(@text); my $distrib_id = ''; my $distrib_rel = ''; foreach (@text) { if ( $_ =~ /^\s*DISTRIB_ID=(.*)$/ ) { $distrib_id = $1; # last DISTRIB_ID value in file used } elsif ( $_ =~ /^\s*DISTRIB_RELEASE=(.*)$/ ) { $distrib_rel = $1; # last DISTRIB_RELEASE value in file used } } if ( $distrib_id =~ /^(Ubuntu|"Ubuntu")\s*$/ ) { $os = "ubuntu"; if ( $distrib_rel =~ /^(.*?)\s*$/ ) { # eliminate trailing blanks, if any $distrib_rel = $1; } if ( $distrib_rel =~ /^"(.*?)"$/ ) { # eliminate enclosing quotes, if any $distrib_rel = $1; } $ver = $distrib_rel; } } } elsif (-f "/etc/debian_version") #possible debian { if (open($relfile, "<", "/etc/issue")){ $line = <$relfile>; if ( $line =~ /debian.*/i){ $os = "debian"; my $relfile1; open($relfile1, "<", "/etc/debian_version"); $ver = <$relfile1>; close($relfile1); } close($relfile); } } $os = "$os" . "," . "$ver"; return ($os); } #----------------------------------------------------------------------------- =head3 acquire_lock Get a lock on an arbirtrary named resource. For now, this is only across the scope of one service node/master node, an argument may be added later if/when 'global' locks are supported. This call will block until the lock is free. Arguments: A string name for the lock to acquire Returns: false on failure A reference for the lock being held. =cut #----------------------------------------------------------------------------- sub acquire_lock { my $lock_name = shift; use File::Path; mkpath("/var/lock/xcat/"); use Fcntl ":flock"; my $tlock; $tlock->{path}="/var/lock/xcat/".$lock_name; open($tlock->{fd},">",$tlock->{path}) or return undef; unless ($tlock->{fd}) { return undef; } flock($tlock->{fd},LOCK_EX) or return undef; return $tlock; } #--------------------- =head3 release_lock Release an acquired lock Arguments: reference to lock Returns: false on failure, true on success =cut #----------------------------------------------------------------------------- sub release_lock { my $tlock = shift; unlink($tlock->{path}); flock($tlock->{fd},LOCK_UN); close($tlock->{fd}); } #------------------------------------------------------------------------------- =head3 get_unique_members Description: Return an array which have unique members Arguments: origarray: the original array to be treated Returns: Return an array, which contains unique members. Globals: none Error: none Example: my @new_array = xCAT::BuildKitUtils::get_unique_members(@orig_array); Comments: =cut #------------------------------------------------------------------------------- sub get_unique_members { my @orig_array = @_; my %tmp_hash = (); for my $ent (@orig_array) { $tmp_hash{$ent} = 1; } return keys %tmp_hash; } #------------------------------------------------------------------------------- =head3 full_path Description: Convert the relative path to full path. Arguments: relpath: relative path cwdir: current working directory, use the cwd() if not specified Returns: Return the full path NULL - Failed to get the full path. Globals: none Error: none Example: my $fp = xCAT::BuildKitUtils::full_path('./test', '/home/guest'); Comments: =cut #------------------------------------------------------------------------------- sub full_path { my ($class, $relpath, $cwdir) = @_; my $fullpath; if (!$cwdir) { #cwdir is not specified $fullpath = Cwd::abs_path($relpath); } else { $fullpath = $cwdir . "/$relpath"; } return $fullpath; } 1;