#!/usr/bin/perluse strict;use warnings;################################################################################## ppc - Patching Prefight Checks## ppc --help (For more information).## Copyright (c) 2009 by Sun Microsystems, Inc. All rights reserved.#################################################################################our $VERSION = '1.0.1';our $DEBUG = 0; our @FILTER = ();use Getopt::Long;use Pod::Usage;use File::Basename;use File::Spec::Functions qw(splitpath);use Cwd qw(abs_path);my $full_path = abs_path($0);my $dir_path = dirname($full_path);# Parse the commandline options.my $p_settings = parse_commandline();print_banner();# Check that the program is being run correctly (as root, on Sol10, global zone...)sanity_checks();# retrieve information about any zones on the system.my $z_data = fetch_zone_data($p_settings->{'alt_root'});# Start testsmy $idr_pkgs_locked = system_validation($p_settings->{'alt_root'},$p_settings->{'danger'}, $z_data, $p_settings->{'alt_root_defined'});pspool_validation($p_settings->{'alt_root'});# If the system check only option has been used, _don't_ do patch validation.if(! $p_settings->{'system_validation_only'} ){ patch_validation($p_settings->{'patch_list'}, $dir_path, $idr_pkgs_locked); space_checking($p_settings->{'alt_root'},$p_settings->{'patch_list'}, $z_data);}# Show the number of messages which were filtered.note(0,scalar(@FILTER)." messages filtered");# If asked for at the commandline, print out all messages which were filtered.if($p_settings->{'show_filtered_msgs'}){ foreach my $msg (@FILTER) { print "(F) $msg\n"; }}################################################################################# FUNCTION: print_banner## ARGUMENTS: NONE## RETURNS: NONE## DESCRIPTION: This function prints a banner at the start of the program.################################################################################sub print_banner{ my $banner=<<HERE;################################################################################# Patching Preflight Checks: $VERSION# Copyright (c) 2009 by Sun Microsystems, Inc. All rights reserved.################################################################################## Notification message id's are 3 digit numbers and appear in the left hand# column. For more information about a notification message, refer to the# documentation using the specific Notification message id.# W = Warning, E = Error.#################################################################################HERE print $banner; print("# Begun: ".localtime()."\n"); my $cmd = "/usr/bin/uname -a"; my $result = `$cmd 2>&1`; if( $? ) { print("# Unable to determine output from uname -a\n"); } else { chomp($result); print("# $result\n"); } print("#"x80); print("\n");}################################################################################# FUNCTION: sanity_checks## ARGUMENTS: NONE## RETURNS: NONE## DESCRIPTION: This function performs checks to ensure that the program is # run in the correct environment (Run as root, run in global zone# run on a Solaris10 system, etc).################################################################################sub sanity_checks{ # Check the E_UID (Need root privileges). if($> != 0) { print("This script must be run as root.\n"); exit; } # Check the script is running on Solaris 10 my $sol_ver = '5.10'; my $cmd = "uname -r"; my $result = `$cmd 2>&1`; chomp($result); if($result !~ /^$sol_ver$/) { print("This script is only compatible with Solaris 10 systems.\n"); exit; } # Check the script is run from the global zone $cmd = "/sbin/zonename"; $result = `$cmd 2>&1`; chomp($result); if($result !~ /^global$/) { print("This script should only be run from the global zone.\n"); exit; }}################################################################################# FUNCTION: space_checking## ARGUMENTS: STRING Alternate root path# ARRAY_REF List of patches to be installed.# HASH_REF Zone data## RETURNS: NONE## DESCRIPTION: This function is the entry point for space checking.################################################################################sub space_checking{ my $alt_root = shift; my $patch_list = shift; my $zone_data = shift; my $pkg_data_dir = '/var/sadm/pkg'; my $final_pkg_data_dir = $alt_root.$pkg_data_dir; note(0,'+'x69); note(0,"+ space_checking"); note(0,'+'x69); my $counter = calculate_install_size($alt_root, $patch_list, $final_pkg_data_dir); my $file_sys_usage = calculate_fs_usage($alt_root,$zone_data); do_space_calculation($counter,$file_sys_usage);}################################################################################# FUNCTION: do_space_calculation## ARGUMENTS: HASH_REF Contains counters from patch analysis# HASH_REF file system usage## RETURNS: NONE## DESCRIPTION: This function keeps a running count of which filesystem is used # for which partition (/dev/md/dsk/d30 -> /usr) and how many# times.################################################################################sub do_space_calculation{ my $counter = shift; my $fs_usage = shift; my %total = (); my @file_systems; # Add an overhead of 50Mb for safety my $overhead = 50000000; # the undo.Z('s) is stored on /var fs. my $var_counter = $counter->{'var'} + $counter->{'undo'}; foreach my $fs ( keys(%$fs_usage) ) { # keep a list of all file systems used push(@file_systems, $fs); $total{$fs} = $overhead; $total{$fs} += ($counter->{'root'} * $fs_usage->{$fs}{'root'}); $total{$fs} += ($counter->{'usr'} * $fs_usage->{$fs}{'usr'}); $total{$fs} += ($var_counter * $fs_usage->{$fs}{'var'}); if($fs_usage->{$fs}{'var'}) { # the transient requirement for twice the space of the # sparse package as it is being patched. $total{$fs} += (2 * $counter->{'largest_sp'}); } # Keep values in kb $total{$fs} = sprintf("%d",$total{$fs} / 1000); } # Fetch the number of free bytes (in kb) for the file systems my $cmd = qq{/usr/sbin/df -b @file_systems}; my @result = `$cmd 2>&1`; chomp(@result); #Filesystem avail #/dev/md/dsk/d10 2383632 #/dev/md/dsk/d30 40711113 # Get rid of header row shift(@result); foreach my $sys (@result) { my($file_s, $avail) = split(/\s+/,$sys); note(-2,"$file_s -> Required: $total{$file_s}kb. Available: ${avail}kb."); # Check if the Required disk space is greater than what is # available on the system. if($total{$file_s} > $avail) { note(201,"There is not enough disk space left on $file_s to complete the patching session. Req: $total{$file_s}kb, Avail: $avail kb"); } else { note(0,"$file_s ok."); } }}################################################################################# FUNCTION: calculate_fs_usage## ARGUMENTS: STRING Alternate root path## RETURNS: NONE## DESCRIPTION: This function keeps a running count of which filesystem is used # for which partition (/dev/md/dsk/d30 -> /usr) and how many# times.################################################################################sub calculate_fs_usage{ my $alt_root = shift; my $zone_data = shift; my %file_system = (); my $rootpath; foreach my $zone ( keys(%{$zone_data}) ) { if($zone eq 'global') { $rootpath = $alt_root."/"; } else { $rootpath = $zone_data->{$zone}{'rootpath'}."/root"; } my $cmd = qq{/usr/sbin/df -b $rootpath $rootpath/var $rootpath/usr}; my @result = `$cmd`; chomp(@result); my $nul; my $filesys; my $disk_relate; $disk_relate = get_file_system($result[1]); $file_system{ $disk_relate }{'root'}++; $disk_relate = get_file_system($result[2]); $file_system{ $disk_relate }{'var'}++; # Check do we have inherit-pkg-dir's defined # and check is one of them /usr if( ($zone ne 'global') && (ref($zone_data->{$zone}{'ipd'}) eq 'ARRAY') && ( map(m{/usr}, @{$zone_data->{$zone}{'ipd'}}) ) ) { # Don't need to add in, since usr is inherited } else { $disk_relate = get_file_system($result[3]); $file_system{ $disk_relate }{'usr'}++; } } foreach my $filesys (keys(%file_system)) { # Initialise all partitions on a filesystem that haven't been defined. if(!defined($file_system{$filesys}{'root'})) { $file_system{$filesys}{'root'} = 0; } if(!defined($file_system{$filesys}{'var'})) { $file_system{$filesys}{'var'} = 0; } if(!defined($file_system{$filesys}{'usr'})) { $file_system{$filesys}{'usr'} = 0; } note(-2,"$filesys => root: $file_system{$filesys}{'root'} var: $file_system{$filesys}{'var'} usr: $file_system{$filesys}{'usr'}"); } return(\%file_system);}################################################################################# FUNCTION: get_file_system## ARGUMENTS: STRING One line from result of "df -b"## RETURNS: STRING Pool name for zfs or fs name for other types.## DESCRIPTION: This function returns either the pool name (for a zfs file# system) or the file system name itself for all other file # system types.################################################################################sub get_file_system{ my $disk_data = shift; my $filesys; my $nul; my $pool; ($filesys, $nul, $nul) = ( $disk_data =~ m{^((/|\w)+)\s+(\d+)$} ); if($filesys !~ m{^/}) { # This must be stored on a zfs pool, so fetch to pool name $filesys =~ m{([^/]+)}; $pool = $1; } else { # Non-zfs $pool = $filesys; } return($pool);}################################################################################# FUNCTION: calculate_install_size## ARGUMENTS: STRING Alternate root path# ARRAY_REF List of patches to be installed.# STRING Path to /var/sadm/pkg directory (alt root added)## RETURNS: HASH_REF Counter of calculated space required split on # /, /var, and /usr partitions.## DESCRIPTION: This function provides a calculation of space required in the # /, /var and /usr partitions for a list of patches.################################################################################sub calculate_install_size{ my $alt_root = shift; my $patch_list = shift; my $final_pkg_data_dir = shift; my %counter; $counter{'root'} = 0; $counter{'var'} = 0; $counter{'usr'} = 0; $counter{'undo'} = 0; $counter{'largest_sp'} = 0; # Fetch the machine class my $cmd = qq{/usr/bin/uname -m}; my $class_type = `$cmd 2>&1`; chomp($class_type); foreach my $patchid (@$patch_list) { note(-2,"<$patchid>"); my $sparse_pkg_list = find_sparse_pkgs($patchid); # Label to jump if we've the wrong hardware class. SP_PKG: foreach my $sparse_pkg (@$sparse_pkg_list) { my $pkg_to_patch = ""; # Fetch the size of the sparse package. Since this # will be copied around several times during patching # it will affect the transient disk space requirement. my $cmd = qq{/usr/bin/du -sk $final_pkg_data_dir/$sparse_pkg}; my $du_result = `$cmd 2>&1`; my ($sparse_pkg_size, $junk) = split(/\s+/,$du_result); # Variable used to decide if we're counting 2 undo.Z # Default is to double. However if a package to be # patched is a SUNW_PKG_HOLLOW=true, then no pspool # undo.Z is created. my $double = 1; # firstly fetch the package which will be patched by the # the sparse package. (e.g. SUNWcakr.u -> PKG=SUNWcakr) my $FUN_PKG; if( !open($FUN_PKG,"<","$patchid/$sparse_pkg/pkginfo") ) { note(215,"Couldn't open $patchid/$sparse_pkg/pkginfo from patch to be applied."); next; } while(<$FUN_PKG>) { if(/^PKG=(.+)/) { $pkg_to_patch = $1; } elsif(/^SUNW_PKG_HOLLOW=true/) { # This package won't have a pspool undo.Z $double = 0; } elsif(/^ARCH=sparc\.(\w+)/) { if($1 ne $class_type) { # wrong hardware class note(-2,"<$sparse_pkg> skipped. Wrong Hardware class."); next SP_PKG; } } } close($FUN_PKG); # Check to see if the pkg to be patched is actually # installed on the system. If it isn't we don't need to # make any space calculation. $cmd = "/usr/bin/pkginfo -R $alt_root $pkg_to_patch"; if( system("$cmd > /dev/null 2>&1") ) { note(-2,"<$sparse_pkg> skipped. Not installed on system."); next; } my $pmap = "$patchid/$sparse_pkg/pkgmap"; my $SP_PKGMAP; if( !open($SP_PKGMAP,"<",$pmap) ) { note(216,"Couldn't open $pmap from patch to be applied."); next; } my $undo_size_this_pkg = 0; while(<$SP_PKGMAP>) { chomp; if(/^\s*$/) { # bug in pkgmap! Shouldn't have empty # lines. next; } my @file_data = split(/\s+/,$_); if($file_data[1] eq 'f') { my $path_to_file = "$alt_root/$file_data[3]"; my $unpatched_filesize = -s $path_to_file; if( defined($unpatched_filesize) ) { # escape any $ chars in filename # SUNWtcatr has files like this $path_to_file =~ s/\$/\\\$/g; my $compressed_size = `/usr/bin/compress -c $path_to_file | wc -c`; $undo_size_this_pkg += $compressed_size; } else { $unpatched_filesize = 0; } # Add up the difference between the file # delivered by the patch and the file # currently on the system, to figure out # how much the filessytem will grow by. # Running totals are kept for /, /var # and /usr seperately. if($file_data[3] =~ m#^(var|usr)#) { $counter{$1} += ($file_data[7] - $unpatched_filesize); } else { $counter{'root'} += ($file_data[7] - $unpatched_filesize); } } } close($SP_PKGMAP); # If SUNW_PKG_HOLLOW is not true, then we'll have 2 undo.Z if($double) { $undo_size_this_pkg *= 2; } $counter{'undo'} += $undo_size_this_pkg; note(-2,"<$sparse_pkg> S: $sparse_pkg_size R:$counter{'root'} U:$counter{'usr'} V:$counter{'var'} Z:$counter{'undo'} ZZ:$undo_size_this_pkg D:$double"); # calc the largest pkg $counter{'largest_sp'} = ($counter{'largest_sp'} >= $sparse_pkg_size ? $counter{'largest_sp'} : $sparse_pkg_size); } } return(\%counter);}################################################################################# FUNCTION: pspool_validation## ARGUMENTS: STRING Alternate Root path## RETURNS: NONE## DESCRIPTION: This function is the entry point for pspool validation tests.################################################################################sub pspool_validation{ my $alt_root = shift; note(0,'+'x69); note(0,"+ pspool_validation"); note(0,'+'x69); my $pkg_data_dir = '/var/sadm/pkg'; my $final_pkg_data_dir = $alt_root.$pkg_data_dir; test_validate_pspool_files_vrs_pkgmap($final_pkg_data_dir); }################################################################################# FUNCTION: test_validate_pspool_files_vrs_pkgmap## ARGUMENTS: STRING Full path to /var/sadm/pkg (including any Root Path)## RETURNS: NONE## DESCRIPTION: This function validates e and i type entries against their# pkgmap entry (for all pspool packages).################################################################################sub test_validate_pspool_files_vrs_pkgmap{ my $base_dir = shift; note(-1,"test_validate_pspool_files_vrs_pkgmap"); my @pspool_pkgmaps = glob("$base_dir/*/save/pspool/*/pkgmap"); foreach my $pkgmap (@pspool_pkgmaps) { my $PSP_PKGMAP; if( !open($PSP_PKGMAP,"<",$pkgmap) ) { note(217,"Couldn't open pspool pkgmap file: $pkgmap"); next; } while(<$PSP_PKGMAP>) { my $pkgmap_entry_sum; my $pkgmap_entry_bytecount; my $checksum = 0; my $bytecount = 0; my $pkgmap_entry_filepath = "empty"; # 1 e preserve kernel/misc/sysinit 0755 root sys 4728 43774 1204210843 # 1 i pkginfo 4814 18906 1206382943 chomp; my @file_data = split(/\s+/,$_); if($file_data[1] eq 'i') { $pkgmap =~ /(.*)pkgmap$/; my $pspool_path = $1; my $i_file_path = ""; if($file_data[2] eq 'pkginfo') { $pkgmap_entry_filepath = $pspool_path."pkginfo"; } else { $pkgmap_entry_filepath = $pspool_path."install/".$file_data[2]; } $pkgmap_entry_sum = $file_data[4]; $pkgmap_entry_bytecount = $file_data[3]; } elsif($file_data[1] eq 'e') { $pkgmap =~ /(.*)pkgmap$/; my $pspool_path = $1; $pkgmap_entry_filepath = $pspool_path."reloc/".$file_data[3]; $pkgmap_entry_sum = $file_data[8]; $pkgmap_entry_bytecount = $file_data[7]; } else { next; } my $SUM; # checksum if( !open($SUM,"<",$pkgmap_entry_filepath) ) { # The file may be stored under ../root/.. # instead of ../reloc/.. my $tmp = $pkgmap_entry_filepath; $tmp =~ s{/reloc/}{/root/}; if( !open($SUM,"<",$tmp) ) { # seriously its not here. note(101,"Couldn't checksum(open) from pspool: $pkgmap_entry_filepath", {'function' => 'test_validate_pspool_files_vrs_pkgmap'}); next; } else { # switch to the ../root/.. entry $pkgmap_entry_filepath = $tmp; } } while (<$SUM>) { $checksum += unpack("%32C*", $_); } close($SUM); $checksum %= 65535; if($checksum != $pkgmap_entry_sum) { note(218,"checksum <$checksum> of $pkgmap_entry_filepath doesn't match pkgmap entry <$pkgmap_entry_sum>"); next; } # bytecount $bytecount = -s $pkgmap_entry_filepath; if(!defined($bytecount)) { note(102,"File error $pkgmap_entry_filepath, won't stat"); next; } if($bytecount != $pkgmap_entry_bytecount) { note(219,"bytecount <$bytecount> of $pkgmap_entry_filepath doesn't match pkgmap entry <$pkgmap_entry_bytecount>"); next; } } close($PSP_PKGMAP); }}################################################################################# FUNCTION: system_validation## ARGUMENTS: STRING Alt Root Path# INT 0/1 Whether tests which modify the system are# run.# HASH_REF contains a hash of hashes with the zone# information or 1 if not a zoned system.## RETURNS: HASH_REF Contains packages locked by IDR's.## DESCRIPTION: This function is the entry point for system validation tests.################################################################################sub system_validation{ my $alt_root = shift; my $dangerous = shift; my $zone_data = shift; my $alt_root_defined = shift; my $idr_pkgs; note(0,'+'x69); note(0,"+ system_validation"); note(0,'+'x69); my $zones_installed = 0; my @zonelist = (); push(@zonelist, keys (%$zone_data)); if($#zonelist > 0) { $zones_installed = 1; } test_fs_type_for_tmp_partition($alt_root); $zones_installed && test_lu_zone_map(\@zonelist, $alt_root); foreach my $zone (@zonelist) { my $rootpath = $alt_root; my $zone_state = ""; if($zone ne 'global') { $rootpath = $zone_data->{$zone}{'rootpath'}."/root"; $zone_state = $zone_data->{$zone}{'state'}; } note(0,"-"x49); note(0,"- Zone: $zone"); note(0,"-"x49); # Only run this test, (which mounts any zones which are in the # installed state), if it has been explicitly asked for on the # commandline. test_check_zone_runnable($zone, $zone_state, $dangerous); test_temp_patch_files($zone, $rootpath); test_partial_pkg_install($zone, $rootpath); test_unfinalised_installf($zone, $rootpath); test_confirm_pkginfo_exists($zone, $rootpath); test_check_var_sadm_pkg_dirs($zone, $rootpath); test_check_var_sadm_pkg_core_files($zone, $rootpath); test_verify_contents_db($zone, $rootpath, $zone_data->{$zone}, $alt_root_defined); $idr_pkgs = test_idrs_installed($zone, $rootpath); } return($idr_pkgs);}################################################################################# FUNCTION: patch_validation## ARGUMENTS: ARRAY_REF Reference to array containing list of patches.# STRING Path to where this program is run from.## RETURNS: NONE## DESCRIPTION: This function is the entry point for all patch validation tests.################################################################################sub patch_validation{ my $patch_list = shift; my $prog_path = shift; my $idr_pkgs_locked = shift; note(0,'+'x69); note(0,'+ patch_validation'); note(0,'+'x69); foreach my $patchid (@$patch_list) { # strip off any trailing slashes $patchid =~ s{/$}{}; note(0,"<$patchid>"); my $sparse_pkg_list = find_sparse_pkgs($patchid); test_idr_locks_package($patchid, $sparse_pkg_list, $idr_pkgs_locked); test_validate_pkgmap($patchid, $sparse_pkg_list); test_pkgmap_check_sums($patchid, $sparse_pkg_list); test_nobody_read_access_to_checkinstall($patchid, $sparse_pkg_list, $prog_path); test_pkginfo_v_pkgmap_classes_param($patchid, $sparse_pkg_list); test_pkgmap_cas($patchid,$sparse_pkg_list); test_script_permissions($patchid,$sparse_pkg_list); }}################################################################################# FUNCTION: test_script_permissions## ARGUMENTS: STRING Patch id.# ARRAY_REF List of sparse packages in patch.## DESCRIPTION: This function checks the permission are set correctly on install# scripts.################################################################################sub test_script_permissions{ my $patchid = shift; my $sparse_pkg_list = shift; my $error = 0; my @top_level_patch_scripts = qw( prebackout postbackout prePatch prepatch postpatch ); note(-1,"test_script_permissions <$patchid>"); foreach my $p_script (@top_level_patch_scripts) { my $full_p_script = "$patchid/$p_script"; my $executable = -x $full_p_script; if($executable) { note(-2,"\t$p_script is executable. ok"); } elsif(!defined($executable)) { note(-2,"\t$p_script doesn't exist. ok"); } else { note(220,"$p_script is not executable in patch $patchid."); } } foreach my $pkg (@$sparse_pkg_list) { my @pkg_cas_list = glob("$patchid/$pkg/install/*"); note(-2,"\t$patchid/$pkg"); foreach my $cas (@pkg_cas_list) { my $executable = -x $cas; if($executable) { note(-2,"\t\t$cas is executable. ok"); } elsif(!defined($executable)) { note(-2,"\t\t$cas doesn't exist. ok"); } else { note(221,"$cas is not executable."); } } } return($error);}################################################################################# FUNCTION: test_pkgmap_cas## ARGUMENTS: STRING Patch id.# ARRAY_REF List of sparse packages in patch.# STRING Path to directory from where script is run ## RETURNS: NONE## DESCRIPTION: This test checks to ensure that all provided Class Action # Scripts in a sparse package are listed in the pkgmap with an "i"# type entry.################################################################################sub test_pkgmap_cas{ my $patchid = shift; my $sparse_pkg_list = shift; my $prog_path = shift; my $error = 0; note(-1,"test_pkgmap_cas <$patchid>"); my $PKGMAP; my @file_list = (); foreach my $pkg (@$sparse_pkg_list) { my $pkgmap_file = "$patchid/$pkg/pkgmap"; my @pkg_cas_list = glob("$patchid/$pkg/install/*"); my @pkgmap_cas_list = (); if( !open($PKGMAP,"<",$pkgmap_file) ) { note(216,"Couldn't open $pkgmap_file from patch to be applied."); next; } while(<$PKGMAP>) { if($_ =~ /^\s+$/) { # This has been tested for elsewhere next; } @file_list = split(/\s+/,$_); if($file_list[1] eq "i") { if($file_list[2] !~ /pkginfo/) { push(@pkgmap_cas_list,$file_list[2]); } } } close($PKGMAP); my %seen; # lookup table my @not_in_pkg_cas_list;# answer # Build lookup table @seen{@pkgmap_cas_list} = (); foreach my $item (@pkg_cas_list) { my($vol,$dir,$file) = splitpath($item); if( !exists( $seen{$file} ) ) { push(@not_in_pkg_cas_list, $item); } } if( @not_in_pkg_cas_list ) { my $not_listed = join(" ", @not_in_pkg_cas_list); note(222,"Not all CAS scripts are listed in pkgmap from patch to be applied: $not_listed"); } note(-2,"\t[$pkg] test_pkgmap_cas: ok"); } return($error);}################################################################################# FUNCTION: test_pkginfo_v_pkgmap_classes_param## ARGUMENTS: STRING Patch id.# ARRAY_REF List of sparse packages in patch.## RETURNS: 1 If # 0 If ## DESCRIPTION: This test compares the classes listed by the CLASSES parameter# in the pkginfo file with the classes used by the pkgmap file.################################################################################sub test_pkginfo_v_pkgmap_classes_param{ my $patchid = shift; my $sparse_pkg_list = shift; my $error = 0; note(-1,"test_pkginfo_v_pkgmap_classes_param <$patchid>"); my $PKGINFO; my $PKGMAP; foreach my $pkg (@$sparse_pkg_list) { my $pkginfo_file = "$patchid/$pkg/pkginfo"; my @class_array; if( !open($PKGINFO,"<",$pkginfo_file) ) { note(215,"Couldn't open $pkginfo_file from patch to be applied."); next; } while(<$PKGINFO>) { if(/^CLASSES=(.+)/) { my $class_list = $1; @class_array = split(/\s+/,$class_list); } } close($PKGINFO); if( !@class_array ) { note(229,"Couldn't find CLASSES parameter in $pkginfo_file from patch to be applied."); next; } my $pkgmap_file = "$patchid/$pkg/pkgmap"; if( !open($PKGMAP,"<",$pkgmap_file) ) { note(216,"Couldn't open $pkgmap_file from patch to be applied."); next; } while(<$PKGMAP>) { my @file_data = split(/\s+/,$_); if( $#file_data < 3) { next; } if($file_data[1] =~ /[a-z]/ && $file_data[1] ne "i") { if( ! map($file_data[2] =~ /^$_$/,@class_array) ) { note(230,"File class for file: $file_data[3] in $pkgmap_file from patch to be applied, is not listed in patch's pkginfo"); } } } close($PKGMAP); note(-2,"\t[$pkg] test_pkginfo_v_pkgmap_classes_param: ok"); } return($error);}################################################################################# FUNCTION: test_nobody_read_access_to_checkinstall## ARGUMENTS: STRING Patch id.# ARRAY_REF List of sparse packages in patch.# STRING Path to directory from where script is run ## RETURNS: NONE## DESCRIPTION: This check is to that user nobody (or user install) has read# access to checkinstall.################################################################################sub test_nobody_read_access_to_checkinstall{ my $patchid = shift; my $sparse_pkg_list = shift; my $prog_path = shift; my $error = 0; note(-1,"test_nobody_read_access_to_checkinstall "); foreach my $pkg (@$sparse_pkg_list) { my $checkinstall = "$prog_path/$patchid/$pkg/install/checkinstall"; if(-e $checkinstall) { my $cmd = 'su - nobody -c "cat '.$checkinstall.'" > /dev/null 2>&1'; if( system($cmd) ) { note(223,"User nobody does not have read access to checkinstall in pkg: $pkg from patch: $patchid!"); } else { note(-2,"\t[$pkg] test_nobody_read_access_to_checkinstall: ok"); } } else { note(224,"No checkinstall for pkg: $pkg in patch: $patchid"); } } return($error);}################################################################################# FUNCTION: test_pkgmap_check_sums## ARGUMENTS: STRING Patch id.# ARRAY_REF List of sparse packages in patch.## RETURNS: 1 If # 0 If ## DESCRIPTION: This check the contents of a package in a patch.################################################################################sub test_pkgmap_check_sums{ my $patchid = shift; my $sparse_pkg_list = shift; my $error = 0; note(-1,"test_pkgmap_check_sums <$patchid>"); foreach my $pkg (@$sparse_pkg_list) { my $cmd = "/usr/sbin/pkgchk -d $patchid $pkg > /dev/null 2>&1"; if( system($cmd) ) { note(225,"pkgchk failed for package: $pkg in patch $patchid"); } else { note(-2,"\t[$pkg] test_pkgmap_check_sums: ok"); } } return($error);}################################################################################# FUNCTION: test_idr_locks_package## ARGUMENTS: ARRAY_REF List of sparse packages in patch.# HASH_REF Packages locked by IDR's## DESCRIPTION: This function Checks to see whether any packages to be patched# are locked by an IDR already on the system.################################################################################sub test_idr_locks_package{ my $patchid = shift; my $sparse_pkg_list = shift; my $idr_pkgs_locked = shift; note(-1,"test_idr_locks_package <$patchid>"); # Check are any idrs on system if( !%{$idr_pkgs_locked} ) { # Nothing to check return; } foreach my $from_patch (@$sparse_pkg_list) { if($idr_pkgs_locked->{$from_patch}) { note(202,"Patch: $patchid will try to patch $from_patch but this is locked by IDR: $idr_pkgs_locked->{$from_patch}"); } }}################################################################################# FUNCTION: test_validate_pkgmap## ARGUMENTS: STRING Patch id.# ARRAY_REF List of sparse packages in patch.## DESCRIPTION: This function performs validation tests on the pkgmap file.################################################################################sub test_validate_pkgmap{ my $patchid = shift; my $sparse_pkg_list = shift; note(-1,"test_validate_pkgmap <$patchid>"); foreach my $pkg (@$sparse_pkg_list) { my $pkgmap_path = "$patchid/$pkg/pkgmap"; my $V_PKGMAP; if( !open($V_PKGMAP,"<",$pkgmap_path) ) { note(216,"Couldn't open pkgmap $pkgmap_path from patch to be applied."); next; } my @pkgmap_data; my %uniq_file; while(<$V_PKGMAP>) { my $line = $_; chomp($line); if($line =~ /^#/) { # Comments are allowed next; } elsif($line =~ /^\s*$/) { # Empty lines are not allowed! # (See CR 6820592 - Patch 124394-09 (SUNWdtlog). note(203,"$pkgmap_path: Found empty line"); next; } elsif($line =~ /^:/) { # Information line next; } else { # populate an array of array refs with the # pkgmap data. my @tmp_array = split(/\s+/,$line); push(@pkgmap_data, \@tmp_array ); # Gather all filenames (to ensure uniqueness) if($tmp_array[1] eq "i") { $uniq_file{$tmp_array[2]}++; } else { $uniq_file{$tmp_array[3]}++; } } } close($V_PKGMAP); my %data_parts = ( "b" => 9, "c" => 9, "d" => 7, "e" => 10, "f" => 10, "i" => 6, "l" => 4, "p" => 7, "s" => 4, "v" => 10, "x" => 7); # x is a guess, can't find an x entry anywhere. Treating it # like a "d" entry. foreach my $file (@pkgmap_data) { # Check that the filetypes are valid. if($file->[1] !~ /[bcdefilpsvx]/) { note(204,"$pkgmap_path: Invalid file type $file->[1] found: ".join(" ",@$file)); next; } # Check that the entry for each filetype has the correct # number of columns of data. if($#$file != ($data_parts{$file->[1]} - 1) ) { note(205,"$pkgmap_path: Invalid number of columns for row: ".join(" ",@$file) ); next; } # Check that file permissions are octal. (or left with # the ? placeholder or a variable) if($file->[1] =~ /[defpv]/) { if($file->[4] !~ /^\$|[?]|[0-7]{4}/) { note(206,"$pkgmap_path: Invalid permission for row: ".join(" ",@$file) ); next; } } # Check that major and minor device numbers are numeric elsif($file->[1] =~ /[cd]/) { if($file->[4] !~ /^\d+$/ || $file->[5] !~ /^\d+$/) { note(207,"$pkgmap_path: Non-numeric value for either the major or minor device number: ".join(" ",@$file) ); next; } } # check that size, cksum and modtime are numeric if($file->[1] =~ /[efiv]/) { if($file->[$#$file] !~ /^\d+$/ || $file->[$#$file -1] !~ /^\d+$/ || $file->[$#$file -2] !~ /^\d+$/) { note(208,"$pkgmap_path: Non-numeric value for either the size,cksum or modtime: ".join(" ",@$file) ); next; } } } # Check that files are unique (There should be only one entry # per file). foreach my $key (keys(%uniq_file)) { if($uniq_file{$key} > 1) { note(209,"$pkgmap_path contains duplicate entries for $key"); next; } elsif($key =~ /PKG_INSTALL_ROOT|BASEDIR|CLIENT_BASEDIR/) { note(210,"$pkgmap_path: $key contains a reserved keyword (PKG_INSTALL_ROOT|BASEDIR|CLIENT_BASEDIR)"); next; } } }}################################################################################# FUNCTION: find_sparse_pkgs## ARGUMENTS: STRING Patch id## RETURNS: ARRAY_REF List of sparse packages## DESCRIPTION: This function searches a patch directory for sparse packages, by# looking for pkginfo files.################################################################################sub find_sparse_pkgs{ my $patchid = shift; my @pkg; # sparse pkgs are directories directly below the patchid my $cmd = qq{/usr/bin/find $patchid -type d}; my @sparse_pkgs = `$cmd 2>&1`; if( $? ) { # find reported an error... note(108,"Error reported by find command: $cmd"); exit(1); } chomp(@sparse_pkgs); foreach my $sp_pkg (@sparse_pkgs) { if($sp_pkg =~ m{$patchid/([^/]+)$}) { push(@pkg, $1); } } if(! @pkg ) { # no directories returned... note(103,"Couldn't find any sparse packages for patch: $patchid!"); } return(\@pkg);}################################################################################# FUNCTION: fetch_zone_data## ARGUMENTS: STRING Base Root path## RETURNS: HASHREF contains a hash of hashes with the zone information.# 1 If zoneadm fails. Assumes zoneadm failed because its not# installed on the system (SUNWCreq-like system).## DESCRIPTION: This function retrieves the zone information and packs it into# a hash.################################################################################sub fetch_zone_data{ my $alt_root = shift; note(-2,"fetch_zone_data"); my %zone = (); my $zone_count = 0; my $fcs = 0; if( length($alt_root) > 1 ) { # remove any trailing / from an alt root. # This is to mitigate a problem in zoneadm which returns # different zone states on a zone on an ABE, depending if the # -R value ends in / or not! $alt_root =~ s{/$}{}; } # Only checking installed zones. (Incomplete zones are never patched). my $cmd = "/usr/sbin/zoneadm -R $alt_root list -ip"; my @result = `$cmd 2>&1`; if(!@result) { # SUNWCreq like system, Zones not supported. note(231,"Call to zoneadm failed. Assuming SUNWCreq-like system"); $zone{'global'} = 1; return(\%zone); } elsif($result[0] =~ /zoneadm: illegal option -- R/) { # FCS system. -R option not supported note(232,"FCS system. -R not supported. Zones on ABE won't be inspected."); # Check are we trying to inspect an ABE. if($alt_root !~ m{^/+$}) { $zone{'global'} = 1; return(\%zone); } else { # Run the zoneadm command without the -R $fcs = 1; $cmd = "/usr/sbin/zoneadm list -ip"; @result = `$cmd 2>&1`; } } chomp(@result); $zone{'global'} = 0; foreach my $zonelist (@result) { my @data = split(/:/,$zonelist); if( $data[1] eq "global" ) { # We don't need to analyse global zone next; } elsif( defined($data[5]) && $data[5] ne "native" ) { # We don't need to analyse non-native (branded) zones next; } else {# FCS machine#1:pandabear_zone1:running:/export/zones/pandabear_zone1#2:pandabear_zone3:running:/export/zones/pandabear_zone3# u5 machine#2242:sparsezone1:running:/zone/sparsezone1:3041be98-e5a2-ca02-dd6e-e51851ab8243:native:shared#-:wholezone1:installed:/zone/wholezone1:9d2fee2b-5898-cf9c-8ba6-97dba0eaafee:native:shared $zone{$data[1]}{'state'} = $data[2]; $zone{$data[1]}{'rootpath'} = $data[3]; if( defined($data[4]) ) { $zone{$data[1]}{'uuid'} = $data[4]; } else { $zone{$data[1]}{'uuid'} = ""; } my $r_switch = "-R $alt_root"; if($fcs) { # We're not inspecting an ABE (not supported). $r_switch = ""; } # Check has the usr fs been inherited from global my $cmd = qq{/usr/sbin/zonecfg -z $data[1] $r_switch info inherit-pkg-dir}; my @result = `$cmd 2>&1`; if(@result) { foreach my $line (@result) { if($line =~ m{dir: (/.+)$}) { push(@{$zone{$data[1]}{'ipd'}}, $1); } } } else { $zone{$data[1]}{'ipd'} = 0; } ++$zone_count; } } my @z_list = keys(%zone); note(-2,"\t$zone_count zones found: @z_list"); # If there are zones on the system, return the hashref containing their # data, otherwise return 1 to indicate that no local zones exist on the # system. return(\%zone);}################################################################################# FUNCTION: test_lu_zone_map## ARGUMENTS: ARRAYREF List of zones on system# STRING Base Root path## RETURNS: NONE## DESCRIPTION: This function verifies that zones installed on the system, match# the /tmp/.alt.lu-zone-map file (if BE is mounted!).################################################################################sub test_lu_zone_map{ my $zonelist = shift; my $alt_root = shift; # need local copy my @zone_list = @{$zonelist}; note(-1,"test_lu_zone_map"); my $map_file = qq{$alt_root/tmp/.alt.lu-zone-map}; my $MAP; # file has non-zero size, => ABE has been mounted and map file will # contain list of installed zones on system. if(-s $map_file) { if ( !open($MAP,"<",$map_file) ) { note(104,"Couldn't open $map_file for reading."); return; } while(<$MAP>) { my($mapped_zone, @rest) = split(/\s+/,$_); for(my $i = 0;$i <= $#zone_list; $i++) { if($zone_list[$i] eq 'global') { splice(@zone_list,$i,1); } elsif($zone_list[$i] =~ /${mapped_zone}/) { splice(@zone_list,$i,1); note(-3,"$mapped_zone found in both"); } else { note(226,"Zone: $zone_list[$i] is not listed by zoneadm"); } } } close($MAP); if(@zone_list) { note(227,"The following zones were listed by zoneadm but were not in $map_file:"); note(1,"@zone_list"); return; } } # Reports ok even if zone map file wasn't found or was of zero size note(-2,"\ttest_lu_zone_map: ok");}################################################################################# FUNCTION: test_idrs_installed## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: HASH_REF Contains list of packages which have been locked# by IDR's.## DESCRIPTION: This function checks to see if IDR's have been installed on the # system and warns if they have.################################################################################sub test_idrs_installed{ my $zone = shift; my $rootpath = shift; my %idr_pkgs; note(-1,"test_idrs_installed <$zone>"); my $cmd = qq{/usr/sbin/patchadd -p -R $rootpath | /usr/bin/grep "^Patch: IDR"}; my @result = `$cmd 2>&1`; chomp(@result); # Patch: IDR141684-01 Obsoletes: Requires: 120012-14 Incompatibles: 120012-15 IDR141684-02 Packages: SUNWlibsasl if( @result ) { foreach my $plist (@result) { if($plist =~ m/^Patch: (IDR\d{6}-\d{2}).+Packages: (.+)$/) { my $idr = $1; my $package_list = $2; my @pkg_list = split(/,\s*/,$package_list); foreach (@pkg_list) { $idr_pkgs{$_} = $idr; } note(233,"IDR: $idr has locked the following packages: $package_list"); } } } else { note(-2,"test_idrs_installed: ok"); } return(\%idr_pkgs);}################################################################################# FUNCTION: test_verify_contents_db## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: NONE## DESCRIPTION: This function verifies that files listed in the contents db are# present and correct on the system.################################################################################sub test_verify_contents_db{ my $zone = shift; my $rootpath = shift; my $zone_data = shift; my $alt_root_defined = shift; note(-1,"test_verify_contents_db <$zone>"); if($zone ne 'global') { if(ref($zone_data->{'ipd'}) eq 'ARRAY') { note(0,"Note: Files on inherited package directories won't be verified: @{$zone_data->{'ipd'}}"); } } my $contents_db = "/var/sadm/install/contents"; my $path_to_cdb = $rootpath.$contents_db; note(-3,"$path_to_cdb"); my $CONTDB; my @symlinks; my @plain_files; if( !open($CONTDB,"<",$path_to_cdb) ) { note(105,"Couldn't open contents db: $path_to_cdb"); return; } while(<$CONTDB>) { chomp; if($_ =~ /^#/) { #skip line if its a comment next; } elsif( $_ =~ m{^/devices} ) { # Skip, due to workings of devfs also See CR47201920. # Links to the /devices tree will be handled below. next; } my @file_data = split(/\s+/,$_); if($file_data[1] eq 's') { push(@symlinks,\@file_data); } elsif($file_data[1] eq 'l') { my($hlink,$source) = split(/=/,$file_data[0]); push(@plain_files,$hlink); } else { push(@plain_files,$file_data[0]); } } close($CONTDB); # Plain files PL_FL: foreach my $p_file (@plain_files) { $p_file = $rootpath."/".$p_file; if(!(-e $p_file)) { if($zone ne 'global') { # if the zone has any inherit-pkg-dir's if(ref($zone_data->{'ipd'}) eq 'ARRAY') { foreach my $ipd (@{$zone_data->{'ipd'}}) { # if the symlink is on any of the inherit-pkg-dir paths if($p_file =~ m{/root/{0,2}$ipd/}) { # don't process because the ABE won't have # mounted the inherit-pkg-dir next PL_FL; } } } } note(234,"File listed in contents db <$zone>, doesn't exist: $p_file", {'function' => 'test_verify_contents_db'}); } } # symlinks SM_LK: foreach my $c_file (@symlinks) { my($symlink,$file) = split(/=/,$c_file->[0]); $symlink = $rootpath."/".$symlink; my $full_path_file; my $symlink_ok = 0; my $e_msg = ""; my $m_id = 3; # If its a zone if($zone ne 'global') { # if the zone has any inherit-pkg-dir's if(ref($zone_data->{'ipd'}) eq 'ARRAY') { foreach my $ipd (@{$zone_data->{'ipd'}}) { # if the symlink is on any of the inherit-pkg-dir paths if($symlink =~ m{/root/{0,2}$ipd/} || $file =~ m{$ipd/}) { # don't process because the ABE won't have # mounted the inherit-pkg-dir next SM_LK; } } } } if( !defined(readlink($symlink)) ) { if($! =~ /No such file or directory/) { $e_msg = "Symlink listed in contents db, doesn't exist: $symlink"; $m_id = 235; } elsif($! =~ /Invalid argument/) { $e_msg = "File listed in contents db, exists but isn't a symlink: $symlink"; $m_id = 236; } else { $e_msg = "Error with symlink: $symlink"; $m_id = 106; } } else { # Check that file which is pointed to exists # if the file listed begins with / then its absolute if($file =~ m{^/}) { $full_path_file = $rootpath."/".$file; } else { # Get the path of the symlink (non absolute path) $full_path_file = unwind_linked_file($symlink, $file, $zone); } if($full_path_file =~ m{^/devices}) { # Skip due to workings of devfs. next; } if(-e $full_path_file) { $symlink_ok=1; } else { # If its a local zone if($zone ne 'global') { # if the zone has any inherit-pkg-dir's if(ref($zone_data->{'ipd'}) eq 'ARRAY') { foreach my $ipd (@{$zone_data->{'ipd'}}) { # if the symlink is on any of the inherit-pkg-dir paths { # don't process because the ABE won't have # mounted the inherit-pkg-dir $symlink_ok = 1; last; } } } } $e_msg = "File: $full_path_file symlinked by: $symlink doesn't exist ($c_file->[3])"; $m_id = 237; } } if( !$symlink_ok ) { note($m_id,$e_msg, { 'zone' => $zone_data, 'function' => 'test_verify_contents_db'}); } }}################################################################################sub unwind_linked_file{ my $link = shift; my $file = shift; my $zone = shift; # figure out the number of parent directories to climb my $climb = ($file =~ s{\.\./}{}g); my($tmp1, $dir, $tmp2) = fileparse($link); # remove last / chop($dir); my @list = split("/",$dir); # Make sure we don't go past the zone's root if($zone ne 'global') { for(my $i = 0; $i <= $#list; $i++) { if($list[$i] eq 'root') { my $stop_unwind = $#list - $i; # take the smallest $climb = ($stop_unwind < $climb ? $stop_unwind : $climb); last; } } } for(0 .. $climb-1) { pop(@list); } $dir = join("/", @list); return($dir."/".$file);}################################################################################# FUNCTION: test_fs_type_for_tmp_partition ## ARGUMENTS: STRING Base Root path## RETURNS: NONE## DESCRIPTION: This function checks that /tmp and /var/run are mounted on a # tmpfs partition, and produces a warning if they are not.################################################################################sub test_fs_type_for_tmp_partition{ my $alt_root = shift; note(-1,"test_fs_type_tmp_partition"); my $cmd = qq{/usr/sbin/df -n $alt_root/tmp $alt_root/var/run}; my @result = `$cmd`; if( $? ) { note(107,"df command returned an error: $cmd"); } #df -n /tmp /var/run #/tmp : tmpfs #/var/run : tmpfs foreach my $partition (@result) { chomp($partition); if($partition =~ m{([\w|/]+)\s+:\s*(\w+)} ) { if($2 ne "tmpfs") { note(211,"Partition: $1 is of type: $2 (not tmpfs!)"); } else { note(-2,"\tPartition: $1 is of type tmpfs, ok."); } } }}################################################################################# FUNCTION: test_check_var_sadm_pkg_core_files## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: NONE## DESCRIPTION: This function checks that for core files left over in the # /var/sadm/pkg tree.################################################################################sub test_check_var_sadm_pkg_core_files{ my $zone = shift; my $rootpath = shift; note(-1,"test_check_var_sadm_pkg_core_files <$zone>"); my $start_dir = "/var/sadm/pkg"; my $complete_path = $rootpath.$start_dir; my $cmd = qq{/usr/bin/find $complete_path -name core -o -name 'core.*' -o -name '*.core'}; my @result = `$cmd`; if( $? ) { note(108,"Error reported by find command: $cmd"); return; } foreach my $path (@result) { chomp($path); note(238,"Core file found in the /var/sadm/pkg tree: $path"); }}################################################################################# FUNCTION: test_check_var_sadm_pkg_dirs## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: NONE## DESCRIPTION: This function checks that no arbitrary directories exist under# the /var/sadm/pkg tree.################################################################################sub test_check_var_sadm_pkg_dirs{ my $zone = shift; my $rootpath = shift; note(-1,"test_check_var_sadm_dirs <$zone>"); my $start_dir = "/var/sadm/pkg"; my $complete_path = $rootpath.$start_dir; my $cmd = "/usr/bin/find $complete_path -type d"; my @result = `$cmd`; if( $? ) { note(108,"Error reported by find command: $cmd"); return; } foreach my $path (@result) { chomp($path); my @dirs; if( $path =~ m{$complete_path/(.*)$} ) { @dirs = split(/\//,$1); } else { # if it doesn't match, skip next; } if($#dirs == 0) { # skip this is the pkg name } elsif($#dirs == 1) { if($dirs[1] !~ /save|install/) { note(239,"Directory path $path has an unexpected directory: \"$dirs[1]\"", {'function' => 'test_check_var_sadm_dirs'}); next; } } elsif($#dirs == 2) { if($dirs[2] ne "pspool") { # check are we in a patch directory below the save directory if(! ($dirs[1] eq "save" && $dirs[2] =~ /\d{6}-\d{1,2}/ ) ) { note(239,"Directory path $path has an unexpected directory: $dirs[2]", {'function' => 'test_check_var_sadm_dirs'}); next; } } } elsif($#dirs == 3) { if($dirs[3] ne $dirs[0]) { note(239,"Directory path $path has an unexpected directory: $dirs[3]", {'function' => 'test_check_var_sadm_dirs'}); next; } } elsif($#dirs == 4) { if($dirs[4] !~ /reloc|install|save|root/) { note(239,"Directory path $path has an unexpected directory: $dirs[4]", {'function' => 'test_check_var_sadm_dirs'}); next; } } elsif($#dirs == 5) { if($dirs[4] eq "save") { if(! ($dirs[5] =~ /\d{6}-\d{1,2}/ ) ) { note(239,"Directory path $path has an unexpected directory: $dirs[5]", {'function' => 'test_check_var_sadm_dirs'}); next; } } elsif($dirs[4] eq "install") { note(239,"Directory path $path has an unexpected directory: $dirs[5]", {'function' => 'test_check_var_sadm_dirs'}); next; } elsif($dirs[4] !~ /reloc|root/) { note(239,"Directory path $path has an unexpected directory"); next; } } }}################################################################################# FUNCTION: test_confirm_pkginfo_exists## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: NONE## DESCRIPTION: This function checks that all installed packages# (/var/sadm/pkg/<pkg_dir>) has an associated pkginfo.################################################################################sub test_confirm_pkginfo_exists{ my $zone = shift; my $rootpath = shift; note(-1,"test_confirm_pkginfo_exists <$zone>"); my $pkg_dir = 0; my $pkginfo_found = 0; my $pkg_data_dir = "/var/sadm/pkg"; my @files = glob("$rootpath/$pkg_data_dir/*"); foreach my $file (@files) { if(-d $file) { ++$pkg_dir; if( !(-e $file."/pkginfo") ) { note(228,"pkginfo file is missing from $file"); } else { ++$pkginfo_found; } } } note(-2,"\t$pkg_dir pkg directories found, $pkginfo_found pkginfo's found");}################################################################################# FUNCTION: test_unfinalised_installf## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: NONE## DESCRIPTION: This function checks the contents db for files which haven't # been finalised using installf.################################################################################sub test_unfinalised_installf{ my $zone = shift; my $rootpath = shift; note(-1,"test_unfinalised_installf <$zone>"); my $contents_db = "/var/sadm/install/contents"; my $path_to_cdb = $rootpath.$contents_db; my $CONTDB; if( !open($CONTDB,"<",$path_to_cdb) ) { note(105,"Couldn't open Contents db: $path_to_cdb for reading."); return; } while(<$CONTDB>) {# /boot/solaris/filelist.safe f none 0644 root sys 261 24977 1221959854 SUNWcakr# /dev d none 0755 root sys SUNWcsr SUNWcsd# /dev/allkmem=../devices/pseudo/mm@0:allkmem s none SUNWcsd# /etc/crypto/certs/SUNWObjectCA=../../../etc/certs/SUNWObjectCA l none SUNWcsr# /devices/pseudo/arp@0:arp c none 44 0 0666 root sys SUNWcsd # Look out for entries with packages marked -SUNW +SUNW if(/[-+]SUNW/) { my $unfinished = $_; chomp($unfinished); my @file_data = split(/\s+/,$unfinished); my $file = $file_data[0]; my @pkg; if($file_data[1] =~ /[ls]/) { @pkg = @file_data[3 .. $#file_data]; } elsif($file_data[1] =~ /[depv]/) { @pkg = @file_data[6 .. $#file_data]; } elsif($file_data[1] eq "c") { @pkg = @file_data[8 .. $#file_data]; } elsif($file_data[1] eq "f") { @pkg = @file_data[9 .. $#file_data]; } else { # unknown, so just grab last item @pkg = $file_data[$#file_data]; } my $pkg_list = join(" ",@pkg); note(240,"File: $file from $pkg_list has not been finalised in contents db"); } } close($CONTDB);}################################################################################# FUNCTION: test_partial_pkg_install## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: NONE## DESCRIPTION: This function checks for partially installed packages, using the # pkginfo command.################################################################################sub test_partial_pkg_install{ my $zone = shift; my $rootpath = shift; note(-1,"test_partial_pkg_install <$zone>"); my $cmd = "/usr/bin/pkginfo -R $rootpath -p"; note(-3,$cmd); my @results = `$cmd 2>&1`; if( @results ) { note(241,"Partially installed packages found, zone <$zone>:"); foreach my $partial_pkg (@results) { my($tmp1,$p_pkg,$tmp2) = split(/\s+/,$partial_pkg); note(2,"\t$p_pkg"); } }}################################################################################# FUNCTION: test_temp_patch_files## ARGUMENTS: STRING Zonename# STRING Root path## RETURNS: NONE## DESCRIPTION: Checks for temporary patching files, which may indicate that a # previous patching job did not complete successfully.################################################################################sub test_temp_patch_files{ my $zone = shift; my $rootpath = shift; note(-1,"test_temp_patch_files <$zone>"); my $any_found = 0; my @tmp_patch_files = qw( /var/sadm/pkg/.save.* /tmp/.ai.pkg.* /var/sadm/pkg/.SUNW* ); my @tmp_lock_files = qw( !I-Lock! !R-Lock! ); foreach my $tmp_file (@tmp_patch_files) { my @list = glob($rootpath.$tmp_file); if( @list ) { note(242,"Temporary patching files found, zone <$zone>:"); foreach my $tmp (@list) { note(2,"\t$tmp"); } $any_found = 1; } } foreach my $tmp_lock (@tmp_lock_files) { $tmp_lock = quotemeta($tmp_lock); my $cmd = qq{/usr/bin/find $rootpath/var/sadm/pkg -name '$tmp_lock'}; my @found_locks = `$cmd`; if( $? ) { note(108,"Error reported by find command: $cmd"); return; } elsif( @found_locks ) { chomp(@found_locks); note(243,"Temporary patching Lock files found, zone <$zone>:"); foreach my $tmp (@found_locks) { note(2,"\t$tmp"); } $any_found = 1; } } if( $any_found < 1 ) { note(-2,"No temporary files found. ok"); }}################################################################################# FUNCTION: test_check_zone_runnable## ARGUMENTS: STRING Zonename# STRING State of Zone## RETURNS: NONE## DESCRIPTION: This check is to ensure that any zones on the system are either# currently running or are mountable.# (In other words, see if the zone's file system can be# successfully written to by pkginstall).################################################################################sub test_check_zone_runnable{ my $zone = shift; if($zone eq 'global') { return; } my $zone_state = shift; my $dangerous = shift; note(-1,"test_check_zone_runnable"); # On an ABE if its mounted (and it must be so that the mount point is # visible), any zone root paths in the ABE are also mounted. if ( $zone_state eq "running" || $zone_state eq "mounted" ) { note(-2,"\tZone: $zone is in state running or mounted: ok"); } else { # Check has an ABE been mounted. If it has, zones in the # installed state, will not be mountable and zoneadm will # return an error. if(-s "/tmp/.alt.lu-zone-map") { note(212,"Unable to check if zone: $zone is mountable, because an ABE has been mounted"); return; } # Only mount the zone if it has been explicitly allowed to do # so from the commandline. (This will change the system state). elsif( $dangerous ) { # First check to see if the zone is mountable my $cmd = "/usr/sbin/zoneadm -z $zone mount"; if( system($cmd) ) { note(213,"Unable to mount zone: $zone"); } else { $cmd = "/usr/sbin/zoneadm -z $zone unmount"; if( system($cmd) ) { note(109,"Error trying to unmount zone: $zone"); } else { note(-2,"\tZone: $zone is mountable: ok"); } } } else { note(214,"Zone: $zone is not running and this script is unable to check if its file systems are mountable."); } }}################################################################################# FUNCTION: note## ARGUMENTS: INT Message type.# STRING Message to be printed.## RETURNS: NONE## DESCRIPTION: This function handles output printing.################################################################################sub note{ my $msg_id = shift; my $msg = shift; my $filter_data = shift; #my %alert_level = ( -3 => "Code", -2 => "Debug", -1 => "TEST", 0 => "Info", 1 => "Error", 2 => "Warning", 3 => "Notice" ); my %alert_level = ( -3 => "Code", -2 => "Debug", -1 => "TEST", 0 => "Info", 1 => "E", 2 => "W", 3 => "N" ); # Check is this a message going to be filtered if( filter_message($msg,$filter_data) ) { return; } my $level = $msg_id; # Notification messages (Warning, Error, Notice) will be 3 digits # Take the first digit of the msg_id for notification messages if($level > 99) { $level = sprintf("%d",$level/100); } # Don't print debug information if Global debugging is turned off. if( ($DEBUG == 0) && ($level < -1) ) { return; } # tidy up any double (or more!) slashes $msg =~ s#/{2,}#/#g; my $marker = " "; if($level > 1) { $marker = "~~"; } elsif($level > 0) { $marker = "!!"; } elsif($level > -3) { $marker = "--"; } else { $marker = "=="; } my $state = $alert_level{$level}; if($level > 0) { # Some messages are follow on's if($msg_id < 99) { $msg_id = ""; } # Add the message id for notification messages $state .= " $msg_id"; } printf("%-5s $marker $msg\n",$state);}################################################################################# FUNCTION: filter_message## ARGUMENTS: STRING Message to be checked# HASHREF Data to use for filtering decision.## RETURNS: INT 0 or 1. If 1, message should not be displayed.## DESCRIPTION: This function decides whether a message should be filtered based# on known information.################################################################################sub filter_message{ my $msg = shift; my $filter_data = shift; if( !defined($filter_data) ) { return(0); } if( $filter_data->{'function'} eq 'test_verify_contents_db') { if( $msg =~ m{/usr/bin/jcontrol doesn't exist \([-+*]*SUNWj6rt\)} ) { # Known feature: CR6400269 push(@FILTER, $msg); return(1); } elsif ($msg =~ m{applications/com_sun_web_ui/jsp doesn't exist \([-+*]*SUNWasu\)} ) { # Known bug: see CR6201495 or CR6638964 push(@FILTER, $msg); return(1); } elsif($msg =~ m{usr/lib/lu/lurootdev doesn't exist \([-+*]*SUNWluu\)} ) { # Known bug: CR6748781 push(@FILTER, $msg); return(1); } elsif($msg =~ m{usr/platform/SUNW,USBRDT-5440/lib/picl/plugins/(libpiclsbl\.so|libpiclsbl\.so\.1) doesn't exist \([-+*]*SUNWpiclu\)} ) { # Known bug: CR6848124 push(@FILTER, $msg); return(1); } #File listed in contents db (global), doesn't exist: /usr/share/mime/application/realplay.xml elsif($msg =~ m{^File listed in contents db.+, doesn't exist: .+usr/share/mime/application/realplay\.xml} ) { # Known bug: CR6669365 push(@FILTER, $msg); return(1); } elsif($msg =~ m{monitors/pnp/edidgen\.vda doesn't exist \([-+*]*SUNWxwpls\)} ) { # Known bug: CR6443571 push(@FILTER, $msg); return(1); } elsif($msg =~ m{libmd5_psr\.so\.1 doesn't exist \([-+*]*SUNWcar\)} ) { # Known bug: CR6541094 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/INST_RELEASE doesn't exist \([-+*]*SUNWadmr\)} ) { # Known bug: CR6400541 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/openwin/lib/locale/en_US.UTF-8/(xview|conv_tables|XLC_LOCALE|XI18N_OBJS|X11|CT|CT_Encoding|app-defaults) symlinked by.+\([-+*]*SUNW([cemnsw]e[au]ow|auaow|[cns]a[fm]ow)\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/openwin/lib/locale/iso_8859_1/libs symlinked.+\([-+*]*SUNW([acensw]euow|auaow|namow)\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/openwin/lib/locale/common/imsscript/S505multi symlinked by.+\([-+*]*SUNW([cemnsw]euow|[cns]amow|[ht]leu|cleu2|hkleu|auaow|nafow|meaow)\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/openwin/lib/locale/C/xview/.text_extras_menu.+\([-+*]*SUNW(namow|auaow|weuow)\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/openwin/lib/locale/(de_DE|es_ES).UTF-8/LC_MESSAGES.+\([-+*]*SUNW(ceuow|weuow|namow)\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/openwin/lib/locale/iso8859-(2|5|9|13)/print symlinked by.+\([-+*]*SUNW[ce]euow\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/openwin/lib/locale/iso8859-(2|5|9)/(OWfontpath|XLC_LOCALE) symlinked by.+\([-+*]*SUNW[ce]euow\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif($msg =~ m{/usr/lib/locale/iso_8859_(2|5|9|13)/LC_CTYPE/LCL_DEF symlinked by.+\([-+*]*SUNW[ec]euos\)} ) { # Known bug: CR6679394 push(@FILTER, $msg); return(1); } elsif( ref($filter_data->{'zone'}) eq 'HASH' ) { if( defined($filter_data->{'zone'}{'ipd'}) ) { my $mdu_check = 0; if (ref($filter_data->{'zone'}{'ipd'}) eq 'ARRAY') { if( ! map(m{/usr},@{$filter_data->{'zone'}{'ipd'}}) ) { $mdu_check = 1; } } else { $mdu_check = 1; } if($mdu_check) { if($msg =~ m{(metadb|metadevadm|metainit|metarecover|metastat|libmeta\.so|libmeta\.so\.1) doesn't exist \([-+*]*SUNWmdu\)} ) { # Known bug: CR6474006 # Affects SUNWmdu installed in wholeroot zone push(@FILTER, $msg); return(1); } } } } } elsif( $filter_data->{'function'} eq 'test_validate_pspool_files_vrs_pkgmap') { if($msg =~ m{SUNWjfpu/reloc/usr/lib/mle/ja/se/(mlefe|sunview)/mode.dat} ) { # Known bug: CR6468917 push(@FILTER, $msg); return(1); } elsif($msg =~ m{SUNWplowr/reloc/etc/default/init} ) { # Known bug: CR6468945 push(@FILTER, $msg); return(1); } } elsif( $filter_data->{'function'} eq 'test_check_var_sadm_dirs') { if($msg =~ m{HPFC.+unexpected directory: (sed|kernel|drv)$} ) { # Known directories push(@FILTER, $msg); return(1); } elsif($msg =~ m{(SUNWfchbar|SUNWmpsvplr|SUNWexplu).+unexpected directory: (build|etc)$} ) { # Known directories push(@FILTER, $msg); return(1); } }}################################################################################# FUNCTION: parse_commandline## ARGUMENTS: NONE## RETURNS: HASHREF## DESCRIPTION: This function parses the commandline parameters, checking for# correctness and returns program settings.################################################################################sub parse_commandline{ my %program_settings; # Parse the commandline options. my $man = 0; my $help = 0; my $usage = 0; my $version = 0; my $sys_only = 0; my $danger = 0; my $filtered = 0; my $verbose = ''; my $alt_root = "/"; my $alt_root_defined = 0; my $patch_location = ""; my @patches; my $options_ok = GetOptions( 'help|h' => \$help, 'man|m' => \$man, 'usage|u' => \$usage, 'version' => \$version, 'systemonly|s' => \$sys_only, 'D' => \$danger, 'F' => \$filtered, 'V' => \$verbose, 'R=s' => \$alt_root, ); # Print documentation level based on input. pod2usage( 'verbose' => 0 ) if !$options_ok; pod2usage( 'verbose' => 0 ) if $usage; pod2usage( -verbose => 1) if $help; pod2usage( 'verbose' => 2 ) if $man; # Print version information if requested if($version) { print("$0 $VERSION\n"); exit(0); } # Turn on verbose messages if($verbose) { $DEBUG = 1; } # Check that patches have been listed or that the system check only option has # been used. if(! @ARGV && !$sys_only ) { print("No patches listed!\n"); pod2usage('verbose' => 0); } else { foreach (@ARGV) { push (@patches, $_); } } # If an Alt Root has been defined, check that it exists if( $alt_root ne "" ) { if( !(-d $alt_root) ) { print("Alt Root $alt_root doesn't exist or isn't a directory!\n"); exit(1); } } if( $alt_root ne "/" ) { $alt_root_defined = 1; } $program_settings{'system_validation_only'} = $sys_only; $program_settings{'alt_root'} = $alt_root; $program_settings{'patch_list'} = \@patches; $program_settings{'danger'} = $danger; $program_settings{'show_filtered_msgs'} = $filtered; $program_settings{'alt_root_defined'} = $alt_root_defined; return(\%program_settings);}__END__=head1 NAMEppc - Patching Prefight Checks=head1 SYNOPSISppc [-D] [-F] [-R B<root_path>] [-s | B<patch_list>]ppc [--help | --man | --usage | --version]=head1 DESCRIPTIONThis program is used to carry out system checks prior to adding patches to a system.=head1 OPTIONSThe following options are supported:=over=item B<-h --help>Print the help summary.=item B<-m --man>Prints the complete man page.=item B<-u --usage>Display a short usage summary.=item B<--version>Display the application version.=item B<-V>Display verbose messages.=item B<-D>Danger. Allow tests which modify the system to be run.=item B<-F>Filtered. Display Filtered messages.=item B<-s>Do system checks only. (No patch validation).=item B<-R>Specify an alternate Root path.=back=head1 EXAMPLES=over=item Example1 - Getting a help summary ppc --help=item Example2 - Run the system check, and analyse 2 patches ppc 123456-01 987654-09=item Example3 - Run the system check only. ppc -s =item Example4 - Run system check only on an Alternate Root ppc -R /.alt.thisside1 -s=item Example5 - Run system check only and display Filtered msgs ppc -s -F=back