All pastes #1595238 Raw Edit

clueless

public text v1 · immutable
#1595238 ·published 2009-10-05 18:34 UTC
rendered paste body
#!/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