#!/usr/bin/env perl -w # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html ##################################################### # # xCAT plugin package to handle kernel modules # ##################################################### package xCAT_plugin::kmodules; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } require xCAT::Table; require xCAT::Utils; require xCAT::TableUtils; require Data::Dumper; require Getopt::Long; require xCAT::MsgUtils; use File::Path qw(mkpath rmtree); use File::Basename qw(basename); use Text::Balanced qw(extract_bracketed); use Safe; my $evalcpt = new Safe; use strict; use warnings; # # Globals # #------------------------------------------------------------------------------ =head1 kmodules This program module file performs kernel module functions Supported commands: lskmodules -- List the kernel modules in one or more: osimage.driverupdatesrc (duds or rpms) kitcomponent.driverpacks (rpms in repodir) osdistro (kernel- rpms) osdistroupdate (kernel- rpms) =cut #------------------------------------------------------------------------------ =head2 Kernel Modules Support =cut #------------------------------------------------------------------------------ #---------------------------------------------------------------------------- =head3 handled_commands Return a list of commands handled by this plugin =cut #----------------------------------------------------------------------------- sub handled_commands { return { lskmodules => "kmodules" }; } #---------------------------------------------------------------------------- =head3 preprocess_request Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- # sub preprocess_request { # # NOT REQUIRED -- no hierarchy for this command # my $req = shift; # return [$req]; # } #---------------------------------------------------------------------------- =head3 process_request Process the kernel modules commands Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- sub process_request { $::request = shift; $::CALLBACK = shift; $::SUBREQ = shift; my $ret; # globals used by all subroutines. $::command = $::request->{command}->[0]; $::args = $::request->{arg}; $::stdindata = $::request->{stdin}->[0]; # figure out which cmd and call the subroutine to process if ( $::command eq "lskmodules" ) { $ret = &lskmodules($::request); } return $ret; } #---------------------------------------------------------------------------- =head3 lskmodules_usage Arguments: Returns: Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- # display the usage sub lskmodules_usage { my $rsp; push @{ $rsp->{data} }, "\nUsage: lskmodules - List kernel modules in specified input \n"; push @{ $rsp->{data} }, " lskmodules [-V|--verbose] [-x|--xml|--XML] [-c|--kitcomponent kit_comp1,kit_comp2,...] [-o|--osdistro os_distro] [-u|--osdistroupdate os_distro_update] [-i|--osimage osimage] \n "; push @{ $rsp->{data} }, " lskmodules [-h|--help|-?] \n"; push @{ $rsp->{data} }, " lskmodules [-v|--version] \n "; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); return 0; } #---------------------------------------------------------------------------- =head3 processArgs Process the command line Arguments: Returns: 0 - OK 1 - just print usage 2 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- sub processArgs { if ( defined ($::args) && @{$::args} ) { @ARGV = @{$::args}; } # parse the options # options can be bundled up like -vV, flag unsupported options Getopt::Long::Configure( "bundling", "no_ignore_case", "no_pass_through" ); Getopt::Long::GetOptions( 'help|h|?' => \$::opt_h, 'kitcomponent|c=s' => \$::opt_c, 'osimage|i=s' => \$::opt_i, 'osdistro|o=s' => \$::opt_o, 'osdistroupdate|u=s' => \$::opt_u, 'verbose|V' => \$::opt_V, 'version|v' => \$::opt_v, 'xml|XML|x' => \$::opt_x, ); # Option -h for Help if ( defined($::opt_h) ) { return 2; } # Option -v for version if ( defined($::opt_v) ) { my $rsp; my $version = xCAT::Utils->Version(); push @{ $rsp->{data} }, "$::command - $version\n"; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); return 1; # no usage - just exit } # Option -V for verbose output if ( defined($::opt_V) ) { $::verbose = 1; $::VERBOSE = 1; } if ( !defined($::opt_c) && !defined($::opt_i) && !defined($::opt_o) && !defined($::opt_u) ) { my $rsp; push @{ $rsp->{data} }, "Specify a search location \n"; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); return 2; } my $more_input = shift(@ARGV); if ( defined($more_input) ) { my $rsp; push @{ $rsp->{data} }, "Invalid input: $more_input \n"; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); return 2; } return 0; } #---------------------------------------------------------------------------- =head3 lskmodules Support for listing kernel modules Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- sub lskmodules { my $rc = 0; # process the command line $rc = &processArgs; if ( $rc != 0 ) { # rc: 0 - ok, 1 - return, 2 - help, 3 - error if ( $rc != 1 ) { &lskmodules_usage; } return ( $rc - 1 ); } if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "Running lskmodules command... "; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); } # Get all the rpms and img files to search based on command input my @sources = &set_sources; if (!(@sources)){ my $rsp; push @{ $rsp->{data} }, "No input search source found."; xCAT::MsgUtils->message( "E", $rsp, $::CALLBACK ); return 1; } # Get the list of kernel modules in each rpm/img file my @modlist; foreach my $source (@sources) { if ( $source =~ /^dud:/ ) { $source =~ s/^dud://; push (@modlist, &mods_in_img($source) ); } else { $source =~ s/^rpm://; push (@modlist, &mods_in_rpm($source) ); } } @modlist = &remove_duplicate_mods(\@modlist); # Return the module list for this rpm/img file my $rsp; foreach my $mn (@modlist) { if ($::opt_x) { my %data_entry; $data_entry{module}->{name}=$mn->{name}; $data_entry{module}->{description}=$mn->{description}; push @{ $rsp->{data} }, \%data_entry; } else { push @{ $rsp->{data} }, $mn->{name}.': '.$mn->{description}; } } #xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); if ( $rsp ) { $::CALLBACK->($rsp); } return $rc; } #---------------------------------------------------------------------------- =head3 set_sources return array of input kernel module sources Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- sub set_sources { my $installdir = xCAT::TableUtils->getInstallDir; my @sources; my %src_data; my $order=1; # OS images (osimage.driverupdatesrc) if ( defined($::opt_i) ) { my $litab = xCAT::Table->new('linuximage'); foreach my $li (split( ',', $::opt_i)) { my ($li_entry) = $litab->getAttribs({'imagename'=>$li},('driverupdatesrc')); if ( !($li_entry) ) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "No driverupdatesrc attribute for osimage $li found. Skipping. "; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } foreach my $dus (split( ',', $li_entry->{'driverupdatesrc'})) { my $base_dus = basename($dus); if ( defined ($src_data{$base_dus}) ) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "Duplicate source $base_dus found in input. Skipping $dus entry in osimage &li driverupdatesrc. Using from $src_data{$base_dus}{'sourcemsg'} instead."; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } else { $src_data{$base_dus}{'sourcemsg'}="osimage $li driverupdatesrc entry $dus"; $src_data{$base_dus}{'source'}=$dus; $src_data{$base_dus}{'order'}=$order++; } } } $litab->close; } # Kit Components (kitcomp.driverpacks) if ( defined($::opt_c) ) { my $kctab = xCAT::Table->new('kitcomponent'); my $krtab = xCAT::Table->new('kitrepo'); foreach my $kc (split( ',', $::opt_c)) { my ($kc_entry) = $kctab->getAttribs({'kitcompname'=>$kc},('kitreponame','driverpacks')); if ( !($kc_entry) ) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "No driverpacks attribute for kitcomponent $kc found. Skipping."; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } my ($kr_entry) = $krtab->getAttribs({'kitreponame'=>$kc_entry->{'kitreponame'}},('kitrepodir')); if ( !($kr_entry) ) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "Kitrepo $kc_entry->{'kitreponame'} not found in database. Error in kitcomponent definition for $kc. Skipping."; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } foreach my $dp (split( ',', $kc_entry->{'driverpacks'})) { if ( defined ($src_data{$dp})) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "Duplicate source $dp found in input. Skipping kitcomponent $kc driverpack entry $dp. Using from $src_data{$dp}{'sourcemsg'} instead."; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } else { $src_data{$dp}{'sourcemsg'}="kitcomponent $kc, kitrepo $kc_entry->{'kitreponame'}"; $src_data{$dp}{'source'}=$kr_entry->{'kitrepodir'}.'/'.$dp; $src_data{$dp}{'order'}=$order++; } } } $kctab->close; } # OS distro update if ( defined($::opt_u) ) { my $outab = xCAT::Table->new('osdistroupdate'); foreach my $ou (split( ',', $::opt_u)) { my ($ou_entry) = $outab->getAttribs({'osupdatename'=>$ou},('dirpath')); if ( !($ou_entry) || !(defined($ou_entry->{'dirpath'})) ) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "No dirpath attribute for osdistroupdate $ou found. Skipping."; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } my $dirpath = $ou_entry->{'dirpath'}; my @kernel_rpms = `find $dirpath -name kernel-*.rpm`; foreach my $krpm (@kernel_rpms) { chomp($krpm); my $base_krpm = basename($krpm); if ( defined ($src_data{$base_krpm}) ) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "Duplicate source $base_krpm found in input. Skipping $krpm in osdistroupdate $ou. Using from $src_data{$base_krpm}{'sourcemsg'} instead."; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } $src_data{$base_krpm}{'sourcemsg'}="osdistroupdate $ou rpm $krpm"; $src_data{$base_krpm}{'source'}=$krpm; $src_data{$base_krpm}{'order'}=$order++; } } $outab->close; } # OS distro if ( defined($::opt_o) ) { my $odtab = xCAT::Table->new('osdistro'); foreach my $od (split( ',', $::opt_o)) { my ($od_entry) = $odtab->getAttribs({'osdistroname'=>$od},('dirpaths')); if ( !($od_entry) ) { # try building dirpath from distro_name/local_arch my $arch = `uname -m`; chomp($arch); $arch = "x86" if ($arch =~ /i.86$/); my $dirpath = $installdir.'/'.$od.'/'.$arch; if (!(-e $dirpath)) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "No dirpaths attribute for osdistro $od found. Skipping. "; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } my @kernel_rpms = `find $dirpath -name kernel-*.rpm`; foreach my $krpm (@kernel_rpms) { chomp($krpm); my $base_krpm = basename($krpm); if ( ! defined ($src_data{$base_krpm}) ) { $src_data{$base_krpm}{'sourcemsg'}="osdistro $od default rpm $krpm"; $src_data{$base_krpm}{'source'}=$krpm; $src_data{$base_krpm}{'order'}=$order++; } } } else { foreach my $dirpath (split( ',', $od_entry->{'dirpaths'})){ my @kernel_rpms = `find $dirpath -name kernel-*.rpm`; foreach my $krpm (@kernel_rpms) { chomp($krpm); my $base_krpm = basename($krpm); if ( defined ($src_data{$base_krpm}) ) { if ($::VERBOSE) { my $rsp; push @{ $rsp->{data} }, "Duplicate source $base_krpm found in input. Skipping $krpm in osdistro $od. Using from $src_data{$base_krpm}{'sourcemsg'} instead."; xCAT::MsgUtils->message( "W", $rsp, $::CALLBACK ); } next; } $src_data{$base_krpm}{'sourcemsg'}="osdistro $od rpm $krpm"; $src_data{$base_krpm}{'source'}=$krpm; $src_data{$base_krpm}{'order'}=$order++; } } } } $odtab->close; } foreach my $base_source (sort {$src_data{$a}{'order'} <=> $src_data{$b}{'order'}} keys %src_data){ push @sources, $src_data{$base_source}{'source'}; } if ($::VERBOSE && @sources) { my $rsp; push @{ $rsp->{data} }, "Searching the following locations for kernel modules: "; push @{ $rsp->{data} }, @sources ; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); } return @sources; } #---------------------------------------------------------------------------- =head3 mods_in_rpm return hash of module names/descriptions found in rpm Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- sub mods_in_rpm { my $krpm = shift; my @modlist; my $tmp_path = "/tmp/lskmodules_expanded_rpm"; mkpath($tmp_path); if (-r $krpm) { if (system ("cd $tmp_path; rpm2cpio $krpm | cpio -idum *.ko > /dev/null 2>&1 ; cd - > /dev/null 2>&1")) { my $rsp; push @{ $rsp->{data} }, "Unable to extract files from the rpm $krpm."; xCAT::MsgUtils->message( "E", $rsp, $::CALLBACK ); rmtree($tmp_path); return; } } else { my $rsp; push @{ $rsp->{data} }, "Unable to read rpm $krpm."; xCAT::MsgUtils->message( "E", $rsp, $::CALLBACK ); rmtree($tmp_path); return; } my @ko_files = `find $tmp_path -name *.ko`; foreach my $ko (@ko_files) { my %mod; chomp($ko); my $name = basename($ko); my $desc = `modinfo -d $ko`; chomp ($desc); if ( $desc =~ /^\s*$/ ) { $desc = " "; } $mod{name} = $name; $mod{description} = $desc; push (@modlist, \%mod); } rmtree($tmp_path); return @modlist; } #---------------------------------------------------------------------------- =head3 mods_in_img return hash of module names/descriptions found in driver update image Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- sub mods_in_img { my $img_file = shift; my @modlist; my $mnt_path = "/tmp/lskmodules_mnt"; mkpath($mnt_path); my $rc = system ("mount -o loop $img_file $mnt_path"); if ($rc) { my $rsp; push @{ $rsp->{data} }, "Mount of driver disk image $img_file failed"; xCAT::MsgUtils->message( "E", $rsp, $::CALLBACK ); rmtree($mnt_path); return; } my @ko_files = `find $mnt_path -name *.ko`; foreach my $ko (@ko_files) { my %mod; chomp($ko); my $name = basename($ko); my $desc = `modinfo -d $ko`; chomp ($desc); if ( $desc =~ /^\w*$/ ) { $desc = " "; } $mod{name} = $name; $mod{description} = $desc; push (@modlist, \%mod); } $rc = system ("umount $mnt_path"); rmtree($mnt_path); return @modlist; } #---------------------------------------------------------------------------- =head3 remove_duplicate_mods return array of unique module names/descriptions hashes from input array Arguments: Returns: 0 - OK 1 - error Globals: Error: Example: Comments: =cut #----------------------------------------------------------------------------- sub remove_duplicate_mods { my $modlist = shift; my %unique_names; my @return_list; foreach my $mn (@$modlist) { if ( defined($unique_names{$mn->{name}}) ) { next;} $unique_names{$mn->{name}} = $mn->{description}; } foreach my $un (sort keys(%unique_names)) { my %mod; $mod{name} = $un; $mod{description} = $unique_names{$un}; push (@return_list, \%mod); } return @return_list; } 1;