windows-nt/Source/XPSP1/NT/tools/postbuildscripts/raiseall.pl
2020-09-26 16:20:57 +08:00

2966 lines
95 KiB
Perl

#!perl -w
package raiseall;
use strict;
use Win32::File qw(GetAttributes SetAttributes);
use File::Basename qw(basename);
use Carp;
use lib $ENV{ "RazzleToolPath" };
use lib $ENV{ "RazzleToolPath" }. "\\PostBuildScripts";
use PbuildEnv;
use ParseArgs;
use Logmsg;
use cksku;
use cklang;
use BuildName;
use GetIniSetting;
# Set the script name for logging
$ENV{ "SCRIPT_NAME" } = "raiseall.pl";
# Platform = Architecutre + Debug_Type (e.g. x86fre)
my( $Language, $Build_Number, $Architecture, $Debug_Type, $Time_Stamp,
$Platform, $Quality, $Op, $fOverride_Safe, $fSafe, $fReplaceAtNumberedLink );
my @ConglomeratorSkus = ('symbolcd', 'ddk', 'hal', 'ifs', 'mantis', 'opk', 'mui');
my %Ordered_Qualities = ( pre => 1,
bvt => 2,
tst => 3,
sav => 4,
idw => 5,
ids => 6,
idc => 7 );
#########################################################################
# #
# Server/Client split sku associations #
# #
my @ServerSkus = ( 'ads', 'bin', 'bla', 'dtc', 'sbs', 'srv', 'sym', 'resources' );
my @ClientSkus = ( 'bin', 'per', 'pro', 'sym', 'winpe', 'upgadv', 'resources', @ConglomeratorSkus );
# #
# #
#########################################################################
sub globex($);
sub Main {
my $local_release_path = GetReleasePath();
if ( !defined $local_release_path ) {
errmsg( "Unable to determine release path on current machine, exiting." );
return;
}
# Search for builds availabe to release
my $build_criteria = "$local_release_path\\$Language\\$Build_Number.$Platform.$ENV{_BuildBranch}.$Time_Stamp";
my @available_builds = grep { $_ if ( -d $_ ) } globex $build_criteria;
#
# Check for conglomerator skus
#
#push @available_builds, grep { $_ if ( -d $_ ) } globex "$local_release_path\\$Language\\$Build_Number.$ENV{_BuildBranch}.$Time_Stamp";
push @available_builds, grep { $_ if ( -d $_ ) } globex "$local_release_path\\$Language\\$Build_Number.$ENV{_BuildBranch}";
#
# Check for language neutral skus
#
push @available_builds, grep { $_ if ( -d $_ ) } globex "$local_release_path\\misc\\$Build_Number.$ENV{_BuildBranch}";
dbgmsg( "Available builds: " );
dbgmsg( $_ ) foreach @available_builds;
if ( !@available_builds ) {
if ( $! != 2 ) {
errmsg("Error querying for builds matching $build_criteria ($!)");
}
else {
errmsg("No builds found matching $build_criteria");
}
return;
}
if ( $Op eq 'raise' ) {
return RaiseAll( $local_release_path, \@available_builds );
}
elsif ( $Op eq "lower" ) {
return LowerAll( $local_release_path, \@available_builds );
}
else {
errmsg( "Internal error -- operation = \"$Op\"" );
return;
}
}
sub RaiseAll {
my $local_release_path = shift;
my $all_available_builds = shift;
# Build number is required
if ( $Build_Number eq '*' ) {
errmsg( "Must specify build number." );
return;;
}
# Quality is required
if ( !defined $Quality ) {
errmsg( "Must specify a quality." );
return;
}
# For raiseall operations we only want to deal with the
# newest build of a set of builds with the same build number
my @available_builds;
my ($cur_build_no_ts, $last_build_no_ts) = ('foo', 'foo');
my @sorted_all_available_builds = sort @$all_available_builds;
for ( my $i = $#sorted_all_available_builds; $i >= 0; $i-- ) {
# Standard builds
if ( $sorted_all_available_builds[$i] =~ /(\d+\.[^\.]+\.[^\.]+)\.\d+-\d+$/ ) {
$cur_build_no_ts = $1;
}
# Conglomerator builds (no time-stamp currently)
elsif ( $sorted_all_available_builds[$i] =~ /([^\\]+)\\(\d+\.[^\.]+)(?:\.\d+-\d+)?$/ ) {
$cur_build_no_ts = "$1\\$2";
}
else {
errmsg( "Unrecognized build format: $sorted_all_available_builds[$i]" );
return;
}
if ( $cur_build_no_ts ne $last_build_no_ts ) {
push @available_builds, $sorted_all_available_builds[$i];
$last_build_no_ts = $cur_build_no_ts;
}
else {
dbgmsg( "Skipping older build: ". basename($sorted_all_available_builds[$i]) );
}
}
# Check available builds for outstanding release
# errors; if there are none, set the *.qly file
# to the appropriate value
my $build;
my $srvrel_errors = 0;
foreach $build ( @available_builds ) {
if ( -e "$build\\build_logs\\SrvRelFailed.txt" ) {
errmsg( "SrvRel failures on ". basename($build) );
errmsg( "Please fix errors and remove $build\\build_logs\\SrvRelFailed.txt" );
$srvrel_errors++;
}
# Update the QLY file if this is not a conglomerator build
elsif ( IsOSBuild( $build ) &&
!ModifyQlyFile( basename($build), $Quality, "$build\\build_logs" ) ) {
errmsg( "Failed updating the QLY file for ". basename($build). ", exiting." );
return;
}
}
if ( $srvrel_errors ) {
errmsg( "Aborting due to $srvrel_errors SrvRel failure". ($srvrel_errors > 1?"s.":".") );
return;
}
#
# Setup parameters needed in ForEachBuild functions
#
my @accessed_shares = ();
my %named_parameters = (
ComputerName => $ENV{COMPUTERNAME},
AccessedShares => \@accessed_shares,
Quality => $Quality,
Language => $Language,
OverrideSafe => $fOverride_Safe
);
#
# Setup the local shares on the release servers
#
logmsg( "Creating release shares..." );
if ( !ForEachBuild( \&CreateSecuredBuildShare,
\%named_parameters,
$Language,
\@available_builds,
$fSafe ) ) {
errmsg( "Failed to create shares, exiting." );
return;
}
# Print out shares created / checked if in debug mode
dbgmsg( "$_ share created/verified." ) foreach @accessed_shares;
#
# Do the DFS work
#
my ( $dfs_map, $dfs_lock ) = GetDfsAccess();
return if ( !defined $dfs_map );
# Next few functions need DFS access
$named_parameters{ DfsMap } = $dfs_map;
logmsg( "Checking numbered shares for older timestamps..." );
if ( !ForEachBuild( \&UnmapInconsistentNumberedLinks,
\%named_parameters,
$Language,
\@available_builds,
$fSafe ) ) {
errmsg( "Problem occurred checking numbered shares" );
$dfs_lock->Unlock();
return;
}
logmsg( "Finding/lowering any old DFS links at same quality..." );
if ( !ForEachBuild( \&RemoveOldLatestDotQlyDfsLinks,
\%named_parameters,
$Language,
\@available_builds,
$fSafe ) ) {
errmsg( "Problem verifying/lowering latest.$Quality and associated DFS links" );
$dfs_lock->Unlock();
return;
}
logmsg( "Creating/verifying DFS links..." );
if ( !ForEachBuild( \&CreateDfsMappings,
\%named_parameters,
$Language,
\@available_builds,
$fSafe ) ) {
errmsg( "Problem creating/verifying DFS links." );
$dfs_lock->Unlock();
return;
}
#
# Do symbol server ops
#
# Override standard SKUs with our own
@{$named_parameters{ DefinedSkus }} = ('sym');
# Only want 'sym' on OS builds, not conglomerator builds
my @os_builds = grep { $_ if IsOSBuild($_) } @available_builds;
logmsg( "Checking symbol server state..." );
if ( !ForEachBuild( \&CreateSymbolServerLink,
\%named_parameters,
$Language,
\@os_builds,
$fSafe ) ) {
errmsg( "Problem updating/verifying symbols server DFS links." );
$dfs_lock->Unlock();
return;
}
logmsg( "Flushing outstanding DFS operations..." );
if ( !(tied %$dfs_map)->Flush() ) {
errmsg( "Problem processing DFS commands." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
dfs_lock->Unlock();
return;
}
#
# Release the inter-machine lock
#
$dfs_lock->Unlock();
# Done if we are not touching latest.*
return 1 if ( $fSafe );
#
# If we are raising to BVT quality, check INI
# file to determine if we should run BVT tests
#
# TODO: a release server may have more than one architecture
# and may have just a fre or chk version, so this call
# probably needs to be put in its own function and
# used with ForEachBuild(), specifying many more
# parameters than just -l and -p
#
if ( lc $Quality eq "bvt") {
my $run_boot_test = &GetIniSetting::GetSettingQuietEx( $ENV{ "_BuildBranch" }, $Language, "RunBVT" );
if (defined $run_boot_test &&
lc $run_boot_test eq "true") {
logmsg( "Starting BVT tests..." );
# run tests - spit out warning if there are problems
if ( !ExecuteProgram::ExecuteProgram("perl $ENV{RazzleToolPath}\\postbuildscripts\\assignbvt.pl -l:$Language -p:". ($Architecture eq '*'?"":$Architecture)) ) {
wrnmsg( "Problem accessing bvt machine (error returned from assignbvt.pl)." );
wrnmsg( " ". ExecuteProgram::GetLastCommand() );
wrnmsg( " $_" ) foreach ExecuteProgram::GetLastOutput();
}
}
}
#
# If we are going to TST quality and this is
# a main lab machine, start CRC computation
#
# This was turned off accidentally for over a month and nobody
# noticed, so going to comment it out for now
#
#if ( lc $Quality eq "tst" && $ENV{ "MAIN_BUILD_LAB_MACHINE" } ) {
# logmsg( "Calling CRC routine..." );
# if ( !ExecuteProgram::ExecuteProgram( "$ENV{RazzleToolPath}\\postbuildscripts\\docrc.cmd $Build_Number" ) ) {
# wrnmsg( "CRC routine reported failure!" );
# wnrmsg( " ". ExecuteProgram::GetLastCommand() );
# wrnmsg( " $_" ) foreach ExecuteProgram::GetLastOutput();
# }
#}
return 1;
}
sub LowerAll {
my $local_release_path = shift;
my $all_available_builds = shift;
my @available_builds = sort @$all_available_builds;
# Build number is normally required
if ( $Build_Number eq '*' ) {
if ( defined $fOverride_Safe ) {
wrnmsg( "Forcibly lowering all builds on machine." );
}
else {
errmsg( "Must specify build number." );
return;;
}
}
# Setting quality is pointless -- let the user know
wrnmsg( "Quality flag '$Quality' ignored for lower operation." ) if ( defined $Quality );
# Setting 'safe' is also pointless
wrnmsg( "-safe ignored for lowerall operation." ) if ( defined $fSafe );
# remove the *.qly file for all OS (not conglomerator) builds
logmsg( "Removing the *.qly file..." );
my $build;
foreach $build ( @available_builds ) {
if ( IsOSBuild( $build ) &&
!DeleteQlyFile( "$build\\build_logs" ) ) {
wrnmsg( "Failed removing the QLY file from ". basename($build) );
return;
}
}
#
# Setup parameters needed in ForEachBuild functions
#
my @accessed_shares = ();
my %named_parameters = (
ComputerName => $ENV{COMPUTERNAME},
AccessedShares => \@accessed_shares,
Language => $Language,
OverrideSafe => $fOverride_Safe
);
#
# Lower the local shares on the release servers
#
logmsg( "Lowering release shares..." );
if ( !ForEachBuild( \&LowerBuildShare,
\%named_parameters,
$Language,
\@available_builds,
$fSafe ) ) {
errmsg( "Failed to remove shares, exiting." );
return;
}
# Print out shares deleted / checked if in debug mode
dbgmsg( "$_ share removed/verified." ) foreach @accessed_shares;
#
# Do the DFS work
#
my ( $dfs_map, $dfs_lock ) = GetDfsAccess();
return if ( !defined $dfs_map );
# Need DFS access in the next few functions
$named_parameters{ DfsMap } = $dfs_map;
logmsg( "Removing appropriate DFS links..." );
if ( !ForEachBuild( \&RemoveDfsMappings,
\%named_parameters,
$Language,
\@available_builds,
$fSafe ) ) {
errmsg( "Problem verifying/removing DFS links" );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
$dfs_lock->Unlock();
return;
}
#
# Do symbol server ops
#
# Note: this is explicitly the last step taken as
# we special-case the symbol server "sku" to
# only remove the link if it is the last one
# remaining. This is done so that the
# symbol link is torn down only when all
# instances of the correspondig build have
# been lowered.
#
# Override standard SKUs with our own
@{$named_parameters{ DefinedSkus }} = ('sym');
# Only want 'sym' on OS builds, not conglomerator builds
my @os_builds = grep { $_ if IsOSBuild( $_ ) } @available_builds;
logmsg( "Checking symbol server state..." );
if ( !ForEachBuild( \&RemoveSymbolServerLink,
\%named_parameters,
$Language,
\@os_builds,
$fSafe ) ) {
errmsg( "Problem removing symbols server DFS links." );
$dfs_lock->Unlock();
return;
}
logmsg( "Flushing outstanding DFS operations..." );
if ( !(tied %$dfs_map)->Flush() ) {
errmsg( "Problem processing DFS commands." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
dfs_lock->Unlock();
return;
}
#
# Release the inter-machine lock
#
$dfs_lock->Unlock();
return 1;
}
sub GetDfsAccess {
my $dfs_root = &GetIniSetting::GetSettingQuietEx( $ENV{ "_BuildBranch" }, $Language, "DFSRootName" );
if ( !defined $dfs_root ) {
errmsg( "Unable to find 'DFSRootName' in INI file for $ENV{_BuildBranch}." );
return;
}
dbgmsg( "Using DFS root $dfs_root" );
my $lock_location = &GetIniSetting::GetSettingQuietEx( $ENV{ "_BuildBranch" }, $Language, "DFSSemaphore" );
if ( !$lock_location ) {
errmsg( "Unable to find DFSSemaphore value in INI file." );
return;
}
# Still using a DFS hash that only gets updated on first
# retrieval -- therefore we need to do our best to have
# only one machine at a time updating the DFS. To that
# extent we have setup a lock mechanism on a globally
# accessible share (with 1 minute MAXIMUM intervals
# between checks - 60000 ms = 1 min)
my $synchronize_lock = new MultiMachineLock ($lock_location, 60000);
if ( !defined $synchronize_lock ) {
errmsg( "Problem acquiring lock using $lock_location" );
return;
}
else {
logmsg( "Acquiring lock for exclusive DFS access..." );
while ( !$synchronize_lock->Lock() ) {
# If we timed out, wait some more
if ( 2 == $synchronize_lock->GetLastError() ) {
# Last error message should contain the machine we are waiting on
timemsg( $_ ) foreach ( split /\n/, $synchronize_lock->GetLastErrorMessage() );
}
else {
errmsg ("Failed to acquire lock.");
errmsg( $_ ) foreach ( split /\n/, $synchronize_lock->GetLastErrorMessage() );
return;
}
}
}
# access DFS through a tied hash
logmsg( "Accessing DFS information..." );
my %dfs_map;
if ( ! tie %dfs_map, 'StaticDfsMap', $dfs_root ) {
errmsg( "Error accessing DFS." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
$synchronize_lock->Unlock();
return;
}
return (\%dfs_map, $synchronize_lock);
}
sub IsOSBuild {
my $build_name = shift;
return scalar($build_name =~ /\d+\.[^\.]+\.[^\.]+\.\d+-\d+$/)?1:0;
}
sub AllowQualityTransition {
my $last_quality = shift;
my $cur_quality = shift;
if ( !exists $Ordered_Qualities{lc $last_quality} ||
!exists $Ordered_Qualities{lc $cur_quality} ) {
return;
}
# Allow transition to sav from any previous quality
elsif ( lc $cur_quality eq 'sav' ) {
return 1;
}
# Allow transition from pre/bvt to anything else
elsif ( lc $last_quality eq 'pre' ||
lc $last_quality eq 'bvt' ) {
return 1;
}
# Don't allow transition from anything else to pre/bvt
elsif ( lc $cur_quality eq 'pre' ||
lc $cur_quality eq 'bvt' ) {
return;
}
# Otherwise allow transitions based on order specified in %Ordered_Qualities
elsif ( $Ordered_Qualities{lc $cur_quality} >= $Ordered_Qualities{lc $last_quality} ) {
return 1;
}
else {
return;
}
}
sub CreateSymbolServerLink {
my $named_parameters = shift;
my ( $dfs_access, $build_number,
$full_build_path, $branch, $platform, $quality, $language,
$build_name ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(BuildNumber),
q(FullBuildPath), q(Branch), q(Platform), q(Quality), q(Language),
q(BuildName) );
return if ( !defined $dfs_access );
# Get current symbol server share
my ( $current_symsrv_share, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $current_symsrv_share ) {
dbgmsg( "No symbol server share defined for $branch -- skipping" );
return 1;
}
# Default writeable share is just the $'d version of the non-writeable one
my $current_writeable_symsrv_path = $current_symsrv_share;
$current_writeable_symsrv_path =~ s/(.)$/$1\$/;
# attach the subpath (if there is one)
$current_writeable_symsrv_path .= "\\$sub_path" if ( $sub_path );
# Default build_logs directory is a sibling to default symbols share
my $current_symsrv_build_logs = $current_writeable_symsrv_path;
$current_symsrv_build_logs =~ s/[^\\]+$/build_logs/;
logmsg( "Verifying symbol server has updated shares for $platform..." );
# Verify that the symbol server has the current build
if ( ! -e $current_writeable_symsrv_path ) {
errmsg( "Cannot see $current_writeable_symsrv_path ($!)." );
return;
}
# Try to make the build_logs directory if it doesn't already exist;
if ( ! -e $current_symsrv_build_logs &&
! ( mkdir $current_symsrv_build_logs, 0777 ) ) {
errmsg( "Failed to create $current_symsrv_build_logs ($!)." );
return;
}
# For some reason we maintain a QLY file in two locations
if ( !ModifyQlyFile( $build_name, $quality, $current_writeable_symsrv_path ) ) {
errmsg ( "Problem verifying/creating $quality.qly file on $current_writeable_symsrv_path." );
return;
}
if ( !ModifyQlyFile( $build_name, $quality, $current_symsrv_build_logs ) ) {
errmsg ( "Problem verifying/creating $quality.qly file on $current_symsrv_build_logs." );
return;
}
# Done if going to SAV quality
return 1 if ( lc $quality eq 'sav' );
#
# DFS work
#
# Check if we need to lower any latest.* shares for this build
if ( !RemoveOldLatestDotQlyDfsLinks( $named_parameters ) ) {
errmsg( "Problem verifying/lowering latest symbol server shares for $platform." );
return;
}
# Remove any older numbered shares (older in timestamp)
if ( !UnmapInconsistentNumberedLinks ( $named_parameters ) ) {
errmsg( "Problem occurred checking numbered shares" );
return;
}
# Now create the DFS link if necessary
if ( !CreateDfsMappings( $named_parameters ) ) {
errmsg( "Problem verifying/creating symbol server DFS links for $platform." );
return;
}
return 1;
}
sub RemoveSymbolServerLink {
my $named_parameters = shift;
my ( $dfs_access, $platform, $branch,
$build_number, $build_name ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Platform), q(Branch),
q(BuildNumber), q(BuildName) );
return if ( !defined $dfs_access );
# Get current symbol server share
my ( $current_symsrv_share, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $current_symsrv_share ) {
dbgmsg( "No symbol server share defined for $branch -- skipping." );
return 1;
}
# Default writeable share is just the $'d version of the non-writeable one
my $current_writeable_symsrv_path = $current_symsrv_share;
$current_writeable_symsrv_path =~ s/(.)$/$1\$/;
# attach the subpath (if there is one)
$current_writeable_symsrv_path .= "\\$sub_path" if ( $sub_path );
# Default build_logs directory is a sibling to default symbols share
my $current_symsrv_build_logs = $current_writeable_symsrv_path;
$current_symsrv_build_logs =~ s/[^\\]+$/build_logs/;
# Now attach subpath (if any) to standard symbol share
$current_symsrv_share .= "\\$sub_path" if ( $sub_path );
# Need all prior DFS actions to have completed
if ( !(tied %$dfs_access)->Flush() ) {
errmsg( "Problem processing DFS commands." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
# We only remove the symbol link when it is the last
# link left -- we will assume if it is the last
# link under the numbered share that it is the last
# relevant link under the latest.* shares as well
$named_parameters->{ BuildID } = $build_number;
if ( !IsLastLinkUnderParent( $named_parameters ) ) {
dbgmsg( "$build_name: leaving symbol links untouched" );
return 1;
}
logmsg( "Verifying symbol server links have been removed for $platform..." );
#
# Remove the QLY files
#
# For some reason we maintain a QLY file in two locations
dbgmsg ( "Deleting QLY file from $current_writeable_symsrv_path" );
if ( !DeleteQlyFile( $current_writeable_symsrv_path ) ) {
errmsg ( "Problem reading/removing QLY file from $current_writeable_symsrv_path." );
return;
}
dbgmsg( "Deleting QLY file from $current_symsrv_build_logs" );
if ( !DeleteQlyFile( $current_symsrv_build_logs ) ) {
errmsg ( "Problem reading/removing QLY file from $current_symsrv_build_logs." );
return;
}
#
# Now remove the DFS link
#
if ( !RemoveDfsMappings( $named_parameters ) ) {
errmsg( "Problem verifying/removing symbol server DFS links for $platform." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
return 1;
}
sub CreateDfsMappings {
my $named_parameters = shift;
my ($dfs_access, $computer_name, $quality, $build_number, $language,
$platform, $time_stamp, $branch, $sku, $fsafe, $fconglomerator_build,
$flang_neutral_build ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(ComputerName), q(Quality), q(BuildNumber), q(Language),
q(Platform), q(TimeStamp), q(Branch), q(Sku), q(SafeFlag), q(IsConglomeratorBuild),
q(IsLangNeutralBuild) );
return if ( !defined $dfs_access );
# Done if going to SAV quality
return 1 if ( lc $quality eq 'sav' );
# Done if we are at a different and LOWER quality than other builds
# in our numbered links (e.g. if our build number has been
# raised to TST and we are just being raised to BVT, we don't
# want to put ourselves in the number link yet)
if ( IsQualityHigherAtNumberedLink( $named_parameters )) {
wrnmsg( "Not creating DFS links!" );
return 1;
}
# latest.* DFS link
# only gets created if the current branch is
# responsible for producing the current SKU
my $latest_dfs_link;
$named_parameters->{ BuildID } = "latest.$quality";
$latest_dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $latest_dfs_link ) {
errmsg( "Do not know how to create latest.* DFS links for $branch / $sku." );
return;
}
# build number link
$named_parameters->{ BuildID } = $build_number;
my $numerical_dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $numerical_dfs_link ) {
errmsg( "Do not know how to create numbered DFS links for $branch / $sku." );
return;
}
# \\machine\share[\<subdir>] that makes up local release path
my ( $machine_share, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $machine_share ) {
errmsg( "Do not know how to create local shares for $branch / $sku." );
return;
}
# Tack on the subpath to the share if it exists
$machine_share .= "\\$sub_path" if ( $sub_path );
# Do the DFS mappings
dbgmsg( "Adding/Verifying: $numerical_dfs_link contains $machine_share" );
if ( !($dfs_access->{$numerical_dfs_link} = $machine_share) ) {
errmsg( "Problem mapping to DFS." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
# Link language neutral builds to misc directory as well
if ( $flang_neutral_build) {
my $misc_dfs_link = $numerical_dfs_link;
$misc_dfs_link =~ s/$language/misc/i;
if ( !($dfs_access->{$misc_dfs_link} = $machine_share) ) {
errmsg( "Problem mapping to DFS." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
}
if ( defined $latest_dfs_link &&
!$fsafe ) {
dbgmsg( "Adding/Verifying: $latest_dfs_link contains $machine_share" );
if ( !($dfs_access->{$latest_dfs_link} = $machine_share) ) {
errmsg( "Problem mapping to DFS." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
}
# Time for some special magic on the DFS servers:
# hide/unhide directories and create *.BLD/*.SRV files.
# Skip this step for conglomerator builds
if ( !$fconglomerator_build &&
!MagicDFSSettings( $named_parameters ) ) {
wrnmsg( "Problem verifying/writing to DFS server machines." );
}
return 1;
}
sub IsQualityHigherAtNumberedLink {
my $named_parameters = shift;
my ( $dfs_access, $quality, $build_number, $branch, $sku ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Quality), q(BuildNumber), q(Branch), q(Sku) );
return if ( !defined $dfs_access );
# build number link
$named_parameters->{ BuildID } = $build_number;
my $numerical_dfs_link = GetDfsLinkName( $named_parameters );
# Don't know if quality is higher at numbered
# link, if we don't know how to make the link
return 0 if ( !defined $numerical_dfs_link );
# If there are no builds, then we are OK
return 0 if ( !exists $dfs_access->{$numerical_dfs_link} );
my @current_builds = @{$dfs_access->{$numerical_dfs_link}};
# Otherwise attempt to find a latest.* link for one of the shares
foreach my $share_to_check ( @current_builds ) {
my @links_to_share = @{ $dfs_access->{$share_to_check} };
next if ( !@links_to_share );
foreach my $link ( @links_to_share ) {
my $build_id = GetBuildIdFromDfsLink( $named_parameters, $link );
# Determine if this is a latest.* link
if ( defined $build_id &&
$build_id =~ /^latest\.(.*)$/ ) {
# Compare quality with our current quality
if ( lc $1 ne lc $quality &&
!AllowQualityTransition( $1, $quality ) ) {
wrnmsg( "Other builds linked to $build_number are at $1 quality:" );
wrnmsg( "($share_to_check)." );
return 1;
}
}
}
}
# Did not find a latest.* higher than our current quality
return 0;
}
sub UnmapInconsistentNumberedLinks {
my $named_parameters = shift;
my ($dfs_access, $build_number, $build_time, $branch, $sku,
$quality) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(BuildNumber), q(TimeStamp), q(Branch), q(Sku),
q(Quality) );
# Done if going to SAV quality
return 1 if ( lc $quality eq 'sav' );
$named_parameters->{ BuildID } = $build_number;
my $dfs_numbered_link = GetDfsLinkName( $named_parameters );
if ( !defined $dfs_numbered_link ) {
errmsg( "Do not know how to create DFS links for $branch / $sku." );
return;
}
my @current_builds_linked_at_number;
@current_builds_linked_at_number = @{$dfs_access->{$dfs_numbered_link}} if ( exists $dfs_access->{$dfs_numbered_link} );
my $ffound_inconsistency;
# Check for any shares linked to number that do not match our specifications
foreach ( @current_builds_linked_at_number ) {
my ($current_share_build_number, $current_share_build_time) =
GetBuildNumberAndTsFromShare( $named_parameters, $_ );
if ( !defined $current_share_build_number ) {
errmsg( "Can't determine build number at $dfs_numbered_link from $current_builds_linked_at_number[0] for $branch / $sku." );
return;
}
# If the build number is not what we expected then something is really wrong
if ( $build_number != $current_share_build_number ) {
errmsg( "Share $_ linked at $dfs_numbered_link" );
return;
}
# Check time-stamp
if ( $build_time lt $current_share_build_time ) {
if ( $fReplaceAtNumberedLink ) {
wrnmsg( "Replacing $build_number ($current_share_build_time) which appears newer than $build_number ($build_time)." );
$ffound_inconsistency = 1;
last;
}
else {
errmsg( "Won't replace $build_number ($current_share_build_time) with $build_number ($build_time) without use of -replace" );
return;
}
}
elsif ( $build_time gt $current_share_build_time ) {
dbgmsg( "Replacing links of older build at $build_number: $build_number ($current_share_build_time)" );
$ffound_inconsistency = 1;
last;
}
}
# Unmap the share if an inconsistency was found
if ( $ffound_inconsistency ) {
return if ( !RemoveDfsLink( $named_parameters ) );
}
return 1;
}
sub RemoveOldLatestDotQlyDfsLinks {
my $named_parameters = shift;
my ($dfs_access, $computer_name, $quality, $build_number, $build_name,
$build_time, $language, $platform, $branch, $sku,
$fsafe ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(ComputerName), q(Quality), q(BuildNumber), q(BuildName),
q(TimeStamp), q(Language), q(Platform), q(Branch), q(Sku),
q(SafeFlag) );
return if ( !defined $dfs_access );
# force release is an option
my $override_safe = $named_parameters->{ q(OverrideSafe) };
# Done if going to SAV quality
return 1 if ( lc $quality eq 'sav' );
my @build_ids = GetBuildIdsFromCurrentLinks( $named_parameters );
# Leave latest.<quality> shares alone
# with the exception of pre and bvt
# if -safe was specified
if ( !$fsafe ) {
$named_parameters->{ BuildID } = "latest.$quality";
my $latest_dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $latest_dfs_link ) {
errmsg( "Do not know how to create DFS links for $branch / $sku." );
return;
}
my @current_latest_builds;
@current_latest_builds = @{$dfs_access->{$latest_dfs_link}} if ( exists $dfs_access->{$latest_dfs_link} );
# Trying to enforce rule that you cannot move from a release quality
# latest.* (tst, idw, ids) to a non-release quality (pre, bvt)
foreach ( @build_ids ) {
if ( /^latest\.(.+)$/ &&
!AllowQualityTransition( $1, $quality ) ) {
if ( !defined $override_safe ) {
errmsg( "Not allowed to move from '$1' to '$quality' quality." );
return;
}
else {
wrnmsg( "Forcibly moving from '$1' to '$quality' quality." );
}
}
}
my $fforce_safe_flag;
my ($current_latest_build_number, $current_latest_build_time) = ('', 0);
# while we are at we will check for inconsistencies
my ($last_build_number, $last_build_time);
my $ffound_inconsistency;
foreach ( @current_latest_builds ) {
($current_latest_build_number, $current_latest_build_time) =
GetBuildNumberAndTsFromShare( $named_parameters, $_ );
if ( !defined $current_latest_build_number ) {
errmsg( "Can't determine build number at $latest_dfs_link from $current_latest_builds[0] for $branch / $sku." );
return;
}
# Consistency check
if ( $last_build_number && $last_build_number ne $current_latest_build_number ||
$last_build_time && $last_build_time ne $current_latest_build_time ) {
wrnmsg( "Inconsistent links found at $latest_dfs_link:" );
wrnmsg( "$current_latest_build_number ($current_latest_build_time) and $last_build_number ($last_build_time)" );
$ffound_inconsistency = 1;
}
($last_build_number, $last_build_time) = ($current_latest_build_number, $current_latest_build_time);
}
# If we found an inconsistency, let's nuke the share
if ( $ffound_inconsistency ) {
wrnmsg( "Links were found to be inconsisent -- removing share" );
$current_latest_build_number = 1;
}
# Usually don't want to overwrite newer builds
if ( $current_latest_build_number &&
($current_latest_build_number > $build_number ||
$current_latest_build_number == $build_number &&
$current_latest_build_time gt $build_time) )
{
if ( !$override_safe ) {
dbgmsg( "Current build for $platform / $sku at $quality is $current_latest_build_number ($current_latest_build_time), switching to '-safe' mode." );
$named_parameters->{ForceSafeFlag}{$build_name} = 1;
$fforce_safe_flag = 1;
} else {
wrnmsg( "Forcibly moving latest.$quality from $current_latest_build_number ($current_latest_build_time) to $build_number ($build_time)." );
}
}
# Make sure we didn't set -safe flag for user
if ( !$fforce_safe_flag ) {
# If current latest.* build exists and does
# not have our build number and timestamp, lower it
if ( $current_latest_build_number &&
($current_latest_build_number != $build_number ||
$current_latest_build_time ne $build_time) ) {
if ( !$ffound_inconsistency ) {
dbgmsg( "Lowering $latest_dfs_link because $current_latest_build_number ($current_latest_build_time) != current build $build_number ($build_time)." );
}
$named_parameters->{ BuildID } = "latest.$quality";
return if ( !RemoveDfsLink( $named_parameters ) );
}
# Also need to lower the current numbered share if we are the
# first entry in our latest.* link, as we don't want other
# shares that are not up at our quality to be accessible
# through the same number as ourselves
if ( !exists $dfs_access->{$latest_dfs_link} ) {
$named_parameters->{ BuildID } = $build_number;
return if ( !RemoveDfsLink( $named_parameters ) );
}
}
}
#
# Special rules for BVT and PRE:
#
# If we are currently at latest.bvt or latest.pre
# and are moving to another quality,
# remove latest.<bvt/pre>
#
foreach ( @build_ids ) {
if ( /^latest\.(bvt|pre)$/i &&
lc $1 ne lc $quality ) {
$named_parameters->{ BuildID } = "latest.$1";
return if ( !RemoveDfsLink( $named_parameters ) );
}
}
return 1;
}
# FUTURE:
# This function in most cases would seem to do the same
# as delete <share_name> already implemtened in the tied hash
# Might consider removing this logic...
sub RemoveDfsMappings {
my $named_parameters = shift;
my ( $dfs_access, $branch, $language, $sku, $build_number,
$flang_neutral_build ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Branch), q(Language), q(Sku), q(BuildNumber),
q(IsLangNeutralBuild) );
my @build_ids = GetBuildIdsFromCurrentLinks( $named_parameters );
my ( $share_name, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $share_name ) {
dbgmsg( "No local share definition for $branch / $sku -- skipping." );
return 1;
}
$share_name .= "\\$sub_path" if ( $sub_path );
my $fsuccess = 1;
foreach ( @build_ids ) {
$named_parameters->{BuildID} = $_;
my $dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $dfs_link ) {
errmsg( "Do not know how to create DFS links for $branch / $sku." );
return;
}
$fsuccess &&= (tied %$dfs_access)->RemoveShareFromLink( $dfs_link, $share_name );
}
# If we are a misc link and the only remaining link is
# the misc DFS link, remove it (we're assuming we must have
# just tore down the last language still raised for it)
if ( $flang_neutral_build ) {
my @links_to_share = $dfs_access->{$share_name};
$named_parameters->{BuildID} = $build_number;
my $misc_dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $misc_dfs_link ) {
errmsg( "Do not know how to create DFS links for $branch / $sku." );
return;
}
$misc_dfs_link =~ s/$language/misc/i;
if ( !@links_to_share ||
!grep {!/$misc_dfs_link/i} @links_to_share ) {
$fsuccess &&= delete $dfs_access->{$share_name};
}
}
return $fsuccess;
}
sub IsSkuLinkedToLatest {
my $named_parameters = shift;
my ( $branch, $sku ) =
GetNamedParameters( $named_parameters,
q(Branch), q(Sku) );
return if ( !defined $branch );
if ( IsClientSku( $named_parameters ) && IsClientBranch( $named_parameters ) ||
IsServerSku( $named_parameters ) && IsServerBranch( $named_parameters ) ) {
return 1;
}
else {
return 0;
}
}
sub IsServerSku {
my $named_parameters = shift;
my ( $sku ) =
GetNamedParameters( $named_parameters,
q(Sku) );
return if ( !defined $sku );
return scalar grep { lc$sku eq $_ } @ServerSkus;
}
sub IsClientSku {
my $named_parameters = shift;
my ( $sku ) =
GetNamedParameters( $named_parameters,
q(Sku) );
return if ( !defined $sku );
return scalar grep { lc$sku eq $_ } @ClientSkus;
}
sub IsServerBranch {
my $named_parameters = shift;
my ( $branch, $language ) =
GetNamedParameters( $named_parameters,
q(Branch), q(Language) );
return if ( !defined $branch );
if ( !exists $named_parameters->{ServerBranches}{$branch} ) {
$named_parameters->{ServerBranches}{$branch} = (&GetIniSetting::GetSettingQuietEx( $branch, $language, 'DFSLatestServerSkus' )?1:0);
dbgmsg( "$branch is a server branch." ) if ( $named_parameters->{ServerBranches}{$branch} );
}
return $named_parameters->{ServerBranches}{$branch};
}
sub IsClientBranch {
my $named_parameters = shift;
my ( $branch, $language ) =
GetNamedParameters( $named_parameters,
q(Branch), q(Language) );
return if ( !defined $branch );
if ( !exists $named_parameters->{ClientBranches}{$branch} ) {
$named_parameters->{ClientBranches}{$branch} = (&GetIniSetting::GetSettingQuietEx( $branch, $language, 'DFSLatestClientSkus' )?1:0);
dbgmsg( "$branch is a client branch." ) if ( $named_parameters->{ClientBranches}{$branch} );
}
return $named_parameters->{ClientBranches}{$branch};
}
sub GetBldFileExtension {
my $named_parameters = shift;
my ( $branch ) =
GetNamedParameters( $named_parameters,
q(Branch) );
return if ( !defined $branch );
# IF this branch is a client branch, or this
# branch is both client and server, use BLD
if ( IsClientBranch( $named_parameters ) ) {
return 'BLD';
}
else {
return 'SRV';
}
}
sub RemoveDfsLink {
my $named_parameters = shift;
my ( $dfs_access, $branch, $build_id, $sku ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Branch), q(BuildID), q(Sku) );
return if ! defined ( $build_id );
if ( IsLastLinkUnderParent( $named_parameters ) ) {
# Delete <build>.BLD / <build>.SRV file
#
# If the BLD/SRV file still exists when we unmap the link,
# there will no longer be a DFS link but the directory
# structure will remain, making it look like there is
# still a DFS link
# If we are the last numbered link, assume we are the
# last relevant latest.* link as well, and remove
# BLD/SRV files from latest.* as well (this code was added
# for the XP client/server split)
my @build_ids;
if ( $build_id =~ /^\d+$/ ) {
@build_ids = GetBuildIdsFromCurrentLinks( $named_parameters );
}
else {
@build_ids = ($build_id);
}
my $bldfile_ext = GetBldFileExtension( $named_parameters );
foreach ( @build_ids ) {
$named_parameters->{BuildID} = $_;
foreach ( GetBldFileDfsPathNames( $named_parameters ) ) {
dbgmsg( "Removing $bldfile_ext file from link's parent directory" );
my @bld_files = grep {$_ if ( ! -d $_ )} globex "$_\\*.$bldfile_ext";
# 0 = success
# 2 = couldn't find file (may be a failure or there may be no BLD file;
# have to treat as a success as there is no way
# to tell the difference - $^E doesn't help)
if ( $! != 0 && $! != 2 ) {
wrnmsg( "Trying to find existing $bldfile_ext file: $!" );
}
# Delete all BLD/SRV files
foreach ( @bld_files ) {
if ( !unlink $_ ) { wrnmsg( "Unable to delete $_ ($!)." ) }
}
}
}
# Make sure we are pointing at the right build_id after all of this
$named_parameters->{BuildID} = $build_id;
}
#
# Now actually remove the DFS link
#
my $dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $dfs_link ) {
errmsg( "Do not know how to create DFS links for $branch / $sku." );
return;
}
dbgmsg( "Removing/Verifying $dfs_link from DFS." );
if ( !delete $dfs_access->{$dfs_link} ) {
errmsg( "Problem lowering $dfs_link from DFS." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
return 1;
}
sub MagicDFSSettings {
my $named_parameters = shift;
my ($dfs_access, $branch, $build_number, $build_name,
$language, $sku, $platform, $quality,
$fsafe ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Branch), q(BuildNumber), q(BuildName),
q(Language), q(Sku), q(Platform), q(Quality),
q(SafeFlag) );
return if ( !defined $dfs_access );
#
# We need all new DFS links to exist in order to find
# new paths, so flush any outstanding commands here
#
if ( !(tied %$dfs_access)->Flush() ) {
errmsg( "Problem processing DFS commands." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
#
# Hide / Unhide paths on the DFS machine as we move
# between pre/bvt and other qualities
#
# NOTE: following original code's precedent and not
# hiding the latest.* subdirs
#
$named_parameters->{ BuildID } = $build_number;
foreach ( GetHideableDfsPathNames( $named_parameters ) ) {
my $attribs = 0; # set to avoid warning / actually first used to get return value
my $fhidden;
my $fhide_share;
if ( !GetAttributes( $_, $attribs ) ) {
wrnmsg( "Unable to get current hidden/unhidden status of $_ ($^E) -- skipping." );
}
$fhidden = $attribs & 2;
$fhide_share = ($quality =~ /^(bvt|pre)$/i);
# See if we need to switch hidden attribute on/off
if ( $fhide_share &&
!$fhidden &&
!SetAttributes( $_, $attribs | 2 ) ) {
wrnmsg( "Could not hide $_ ($^E)." );
}
elsif ( !$fhide_share &&
$fhidden &&
!SetAttributes( $_, $attribs & ~2 ) ) {
wrnmsg( "Could not unhide $_ ($^E)." );
}
}
#
# Write <build>.BLD / <build>.SRV file
#
my @builds_to_touch = ( $build_number );
if ( !$fsafe ) { push @builds_to_touch, "latest.$quality" }
my $bldfile_ext = GetBldFileExtension( $named_parameters );
foreach ( @builds_to_touch ) {
$named_parameters->{ BuildID } = $_;
foreach ( GetBldFileDfsPathNames( $named_parameters ) ) {
my $fbldfile_already_set;
my @bld_files = grep { $_ if ( ! -d $_ ) } globex "$_\\*.$bldfile_ext";
# 0 = success
# 2 = couldn't find file (may be a failure or there may be no BLD file;
# have to treat as a success as there is no way
# to tell the difference - $^E doesn't help)
if ( $! != 0 && $! != 2 ) {
wrnmsg( "Trying to find existing $bldfile_ext file: $!" );
}
# If there is more than one, delete them all and start anew
elsif ( @bld_files > 1 &&
! unlink @bld_files ) {
wrnmsg( "Multiple *.$bldfile_ext files found at $_ -- unable to delete ($!)." );
}
# If there is just one, see if it matches current criteria
elsif ( 1 == @bld_files ) {
if ( $bld_files[0] =~ /\\$build_name.$bldfile_ext$/ ) {
unless ( open BLDFILE, "$bld_files[0]" ) {
wrnmsg( "Unable to read $bld_files[0] ($!)." );
next;
}
my $cur_bld_quality = <BLDFILE>;
close BLDFILE;
chomp $cur_bld_quality;
if ( lc $quality eq lc $cur_bld_quality ) {
# Current bld file matches all of our criteria
$fbldfile_already_set = 1;
}
}
# If something about the current bld file did not match,
# delete it in preparation for creating a new one
if ( !$fbldfile_already_set &&
! unlink $bld_files[0] ) {
wrnmsg( "Unable to delete $bld_files[0] ($!)." );
}
}
# Write out new BLD file if necessary
if ( !$fbldfile_already_set ) {
unless ( open WRITE_BLDFILE, "> $_\\$build_name.$bldfile_ext" ) {
wrnmsg( "Unable to write $_\\$build_name.$bldfile_ext ($!)." );
next;
}
print WRITE_BLDFILE "$quality\n";
close WRITE_BLDFILE
}
}
}
return 1;
}
sub GetNamedParameters {
my $named_parameters = shift;
my ( $name, @parameter_list );
while ( $name = shift ) {
if ( !exists $named_parameters->{ $name } ) {
my $called_by = (caller 1)[3];
errmsg( "Undefined parameter \"$name\" to $called_by." );
return;
}
push @parameter_list, $named_parameters->{ $name };
}
return @parameter_list;
}
sub ForEachBuild {
my $func = shift;
my $named_parameters = shift;
my $language = shift;
my $builds = shift;
my $fsafe = shift;
my $build;
foreach $build ( @$builds ) {
my ( $build_number, $arch, $debug_type, $branch, $time_stamp );
my $fconglomerator_build = 0;
my $flang_neutral_build = 0;
# Normal build
if ( IsOSBuild( $build ) ) {
( $build_number, $arch, $debug_type, $branch, $time_stamp ) = build_name_parts( $build );
}
# Conglomerator skus
elsif ( $build =~ /([^\\]+)\\(\d+)\.([^\.]+)(?:\.(\d+-\d+))?$/ ) {
( $build_number, $arch, $debug_type, $branch, $time_stamp ) = ( $2, '', '', $3, $4 || 0 );
$flang_neutral_build = (lc$1 eq 'misc')?1:0;
$fconglomerator_build = 1;
}
if ( !defined $build_number ) {
errmsg( "Unable to determine build information for $build." );
return;
}
my $platform = "$arch$debug_type";
# use already defined skus or default to looking them up
my @skus;
if ( exists $named_parameters->{ DefinedSkus } ) {
@skus = @{$named_parameters->{ DefinedSkus }};
}
elsif ( $fconglomerator_build ) {
@skus = GetSkusForConglomeratorBuild( $build );
}
else {
@skus = GetSkusForBuild( $build, $language, $debug_type, $arch );
}
if ( !@skus ) {
wrnmsg( "No skus defined for $build ($language) - skipping." );
# Skip this build
next;
}
# Setup common parameters for functions
$named_parameters->{ FullBuildPath } = $build;
if ( $fconglomerator_build ) {
($named_parameters->{ BuildName }) = $build =~ /\\([^\\]+\\[^\\]+)$/;
}
else {
$named_parameters->{ BuildName } = basename( $build );
}
$named_parameters->{ BuildNumber } = $build_number;
$named_parameters->{ DebugType } = $debug_type;
$named_parameters->{ Branch } = $branch;
$named_parameters->{ Platform } = $platform;
$named_parameters->{ TimeStamp } = $time_stamp;
$named_parameters->{ IsConglomeratorBuild } = $fconglomerator_build;
$named_parameters->{ IsLangNeutralBuild } = $flang_neutral_build;
if ( defined $fsafe ||
$named_parameters->{ForceSafeFlag}{$named_parameters->{ BuildName }}) {
$named_parameters->{ SafeFlag } = 1;
}
else {
$named_parameters->{ SafeFlag } = 0;
}
# Loop through all supported flavors
my $sku;
foreach $sku ( @skus ) {
$named_parameters->{ Sku } = $sku;
# Client/server split: skip skus
# that are not associated with branch
next if ( !IsSkuLinkedToLatest( $named_parameters ) );
if ( !&$func( $named_parameters ) ) {
errmsg( "Problem occurred during processing of $sku." );
return;
}
}
}
return 1;
}
sub IsLastLinkUnderParent {
my $named_parameters = shift;
my ( $dfs_access, $build_id, $branch, $sku ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(BuildID), q(Branch), q(Sku) );
return if ( !defined $dfs_access );
my $dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $dfs_link ) {
errmsg( "Do not know how to create DFS links for $branch / $sku." );
return;
}
my ( $cur_share_name, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $cur_share_name ) {
errmsg( "Do not know how to create release shares for $branch / $sku." );
return;
}
$cur_share_name .= "\\$sub_path" if ( $sub_path );
#
# First check that we are the last entry in the current dfs link
#
my @cur_dfs_links;
@cur_dfs_links = @{ $dfs_access->{$dfs_link} } if ( exists $dfs_access->{$dfs_link} );
if ( !@cur_dfs_links ||
@cur_dfs_links != 1 ||
lc $cur_share_name ne lc $cur_dfs_links[0] ) {
return;
}
#
# Now check that we are the last link under our parent directory
#
# Construct full path to dfs link's parent directory
my $full_dfs_link = (tied %$dfs_access)->GetDfsRoot(). "\\$dfs_link";
my ($parent_directory) = $dfs_link =~ /^(.*)\\.*$/;
$parent_directory = (tied %$dfs_access)->GetDfsRoot(). '\\'. $parent_directory;
# Loop through all directory entries looking for
# any subdirectories that do not match our share
# name
my @parent_entries = grep { $_ if ( -d $_ ) } globex "$parent_directory\\*";
foreach ( @parent_entries ) {
# It is a directory -- if it isn't us assume
# it refers to another existing link
# WARNING: Special case the 'test' subdirectory
# so we don't count it during
# this check (we will clean up if
# all that is stopping us is 'test')
if ( lc $_ ne lc $full_dfs_link &&
$_ !~ /\\test$/i ) {
return;
}
}
# We must be the only link under our parent directory
return 1;
}
sub GetSkusForBuild {
my $build = shift;
my $language = shift;
my $build_type = shift;
my $architecture = shift;
my %skus = cksku::GetSkus( $language, build_arch( $build ) );
my @returned_skus;
@returned_skus = ( sort keys %skus );
# WinPE image
if ( lc $language eq 'usa' &&
lc $build_type eq 'fre' ) {
push @returned_skus, 'winpe';
}
# UpgAdv image
if ( lc $architecture eq 'x86' &&
lc $build_type eq 'fre' ) {
push @returned_skus, 'upgadv';
}
# Another special case for PSU:
# need to link to 'resources'
if ( lc $language eq 'psu' ) {
push @returned_skus, 'resources';
}
# Special case 'bin' -- everyone gets one
push @returned_skus, 'bin';
return @returned_skus;
}
sub GetSkusForConglomeratorBuild {
my $build = shift;
my @possible_skus = @ConglomeratorSkus;
# BensonT: Non US builds do not use symbolcd
if (lc$Language ne "usa") {
@possible_skus = grep {lc$_ ne 'symbolcd'} @possible_skus;
}
my @skus;
foreach ( @possible_skus )
{
push @skus, $_ if ( -e "$build\\$_" );
}
return @skus;
}
#
# Returns:
#
# ( share, subpath )
# An array consisting of the full-share name (\\server\share) and
# a subpath. \\server\share\subpath would take you to your
# expected destination
#
sub GetShareName {
my $named_parameters = shift;
my ( $computer_name, $build_name, $build_number,
$language, $platform, $branch,
$sku, $fconglomerator_build, $flang_neutral_build,
$timestamp ) =
GetNamedParameters( $named_parameters,
q(ComputerName), q(BuildName), q(BuildNumber),
q(Language), q(Platform), q(Branch),
q(Sku), q(IsConglomeratorBuild), q(IsLangNeutralBuild),
q(TimeStamp) );
return if ( !defined $computer_name );
my $lcsku = lc $sku;
# symbol server shares
if ( $lcsku eq 'sym' ) {
my $symbol_server_share = &GetIniSetting::GetSettingQuietEx( $branch, $language, 'SymFarm' );
if ( !$symbol_server_share ) {
dbgmsg( "No INI file setting for 'SymFarm' in $branch / SymFarm." );
return;
}
return ("$symbol_server_share", "$language\\$build_name\\symbols.pri");
}
# special "skus"
if ( $fconglomerator_build ) {
return ("\\\\$computer_name\\$build_number.$branch.".($flang_neutral_build?'misc':$language), $sku);
}
# Standard skus
else {
# note the special case for "bin"
my $subdir;
$subdir = ($lcsku eq 'bin'?"":$sku);
return ("\\\\$computer_name\\$build_number.$platform.$branch.$timestamp.$language", $subdir);
}
}
sub GetBuildNumberAndTsFromShare {
my $named_parameters = shift;
my $build = shift;
my ( $branch, $language, $platform, $sku, $fconglomerator_build,
$flang_neutral_build ) =
GetNamedParameters( $named_parameters,
q(Branch), q(Language), q(Platform), q(Sku), q(IsConglomeratorBuild),
q(IsLangNeutralBuild) );
my ($build_number, $timestamp);
#
# Note: may have to reach into a share and
# extract some information if it is
# not readily apparent from the name
# of the share (as is the case with main)
#
# Symbol server points to shares
# containing the fully-qualified build-name
# (build_num.platform.branch.time)
if ( lc $sku eq 'sym' ) {
$build_number = build_number( $build );
$timestamp = build_date( $build );
}
# Special "skus"
elsif ( $fconglomerator_build &&
$build =~ /^\\\\[^\\]+\\(\d+)\.[^\.]+(?:\.(\d+-\d+))?\.([^\\]+)\\$sku$/i &&
$flang_neutral_build && lc$3 eq 'misc' ||
!$flang_neutral_build && lc$3 eq lc$language ) {
$build_number = $1;
$timestamp = $2 || 0;
}
# Standard skus
elsif ( !$fconglomerator_build &&
$build =~ /^\\\\[^\\]+\\(\d+)\.$platform\.[^\.]+\.(\d+-\d+)\.$language(?:\\$sku)?$/i ) {
$build_number = $1;
$timestamp = $2;
}
else {
# Error - don't recognize the format
return;
}
return ($build_number, $timestamp);
}
sub GetDfsLinkName {
my $named_parameters = shift;
my ($branch, $language, $build_id, $platform, $sku,
$fconglomerator_build ) =
GetNamedParameters( $named_parameters,
q(Branch), q(Language), q(BuildID), q(Platform), q(Sku),
q(IsConglomeratorBuild) );
return if ( !defined $branch );
my $lcsku = lc $sku;
# Used to add .srv onto skus that appear for both
# the client and server in the latest.* links
my $sku_name_suffix = '';
# If we haven't looked for DFSAlternateBranchName
# in the INI file, do so now
my $dfs_branch_name;
if ( !exists $named_parameters->{DefaultDFSBranches}{$branch} ) {
my $alt_branch_name = &GetIniSetting::GetSettingQuietEx( $branch, $language, 'DFSAlternateBranchName' );
# Default to the branch name
if ( !$alt_branch_name) {
$named_parameters->{DefaultDFSBranches}{$branch} = $branch;
}
else {
$named_parameters->{DefaultDFSBranches}{$branch} = $alt_branch_name;
}
}
$dfs_branch_name = $named_parameters->{DefaultDFSBranches}{$branch};
# Special behaviors on latest.* links
if ( $build_id =~ /^latest/i ) {
# If we haven't looked for DFSAlternateLatestBranchName
# in the INI file, do so now
if ( !exists $named_parameters->{LatestDFSBranches}{$branch} ) {
my $dfs_latest_branch_name = &GetIniSetting::GetSettingQuietEx( $branch, $language, 'DFSAlternateLatestBranchName' );
# Default to the current DFS branch name
if ( !$dfs_latest_branch_name) {
$named_parameters->{LatestDFSBranches}{$branch} = $named_parameters->{DefaultDFSBranches}{$branch};
}
else {
$named_parameters->{LatestDFSBranches}{$branch} = $dfs_latest_branch_name;
}
}
$dfs_branch_name = $named_parameters->{LatestDFSBranches}{$branch};
# If both client and serer support a sku,
# append .srv onto the server version.
# If this branch is both client and server,
# then don't use the .srv extension
if ( IsClientSku( $named_parameters ) &&
IsServerSku( $named_parameters ) &&
IsServerBranch( $named_parameters ) &&
!IsClientBranch( $named_parameters ) ) {
$sku_name_suffix = '.srv';
}
}
# Special "skus"
if ( $fconglomerator_build ) {
return "$dfs_branch_name\\$language\\$build_id\\". ($lcsku eq 'symbolcd'?'symcd2':$lcsku). $sku_name_suffix;
}
# Standard skus and symbol server sku
else {
return "$dfs_branch_name\\$language\\$build_id\\$platform\\$lcsku". $sku_name_suffix;
}
}
sub GetBuildIdsFromCurrentLinks {
my $named_parameters = shift;
my ($dfs_access, $build_number, $branch, $platform, $language, $sku ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(BuildNumber), q(Branch), q(Platform), q(Language), q(Sku) );
return if ( !defined $dfs_access );
my ( $cur_share_name, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $cur_share_name ) {
errmsg( "Do not know how to create release shares for $branch / $sku." );
return;
}
$cur_share_name .= "\\$sub_path" if ( $sub_path );
my @build_ids;
# Return all recognized build IDs we are currently linked to
# (expecting <build_number> and latest.* entries)
my @current_links = @{ $dfs_access->{$cur_share_name} } if ( exists $dfs_access->{$cur_share_name} );
foreach ( @current_links ) {
my $next_build_id;
$next_build_id = GetBuildIdFromDfsLink( $named_parameters, $_ );
if ( $next_build_id ) {
push @build_ids, $next_build_id;
}
}
return @build_ids;
}
sub GetBuildIdFromDfsLink {
my $named_parameters = shift;
my $build = shift;
my ( $branch, $platform, $language, $sku,
$fconglomerator_build, $flang_neutral_build ) =
GetNamedParameters( $named_parameters,
q(Branch), q(Platform), q(Language), q(Sku),
q(IsConglomeratorBuild), q(IsLangNeutralBuild) );
my $lcsku = lc $sku;
my $build_id;
# Special "skus"
if ( $fconglomerator_build ) {
my $lang = $flang_neutral_build?'misc':$language;
my $dfs_sku_name = $lcsku eq 'symbolcd'?'symcd2':$sku;
$dfs_sku_name .= '(?:\.srv)?';
($build_id) = $build =~ /^[^\\]+\\$lang\\([^\\]+)\\$dfs_sku_name$/i;
}
# Standard skus and symbol server sku
else {
($build_id) = $build =~ /^[^\\]+\\$language\\([^\\]+)\\$platform\\$sku(?:\.srv)?$/i;
}
return $build_id;
}
sub GetWriteableDfsShares {
my $named_parameters = shift;
my ($dfs_access, $branch) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Branch) );
return if ( !defined $dfs_access );
# All known branches use the same writeable share format
if ( $branch ) # TRUE
{
my @dfs_servers = (tied %$dfs_access)->GetDfsHosts();
if ( !@dfs_servers ) {
errmsg( "Unable to retrieve hosting machines for DFS root ". (tied %$dfs_access)->GetDfsRoot(). "." );
errmsg( $_ ) foreach ( split /\n/, StaticDfsMap::GetLastErrorMessage() );
return;
}
# Point to writeable shares
s/(\\\\[^\\]+).*/$1\\writer\$\\release/ foreach ( @dfs_servers );
return @dfs_servers;
}
# Unknown branch
else {
return;
}
}
sub GetHideableDfsPathNames {
my $named_parameters = shift;
my ( $dfs_access, $branch, $language, $build_id, $sku ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Branch), q(Language), q(BuildID), q(Sku) );
return if ( !defined $dfs_access );
# Get writeable DFS share for our branch
my @writeable_dfs_shares = GetWriteableDfsShares( $named_parameters );
if ( !@writeable_dfs_shares ) {
dbgmsg( "Unknown writeable DFS share for $branch." );
return;
}
my $dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $dfs_link ) {
errmsg( "Do not know how to create $build_id DFS link for $branch / $sku." );
return;
}
# Want the path up to latest.* or #
# (build_id): main\usa\<build_id>
$dfs_link =~ s/^(.*$build_id)\\.*$/$1/;
# All known branches use the same writeable share format
if ( $branch ) # TRUE
{
return map {"$_\\$dfs_link"} @writeable_dfs_shares;
}
# Unknown branch
else {
return;
}
}
sub GetBldFileDfsPathNames {
my $named_parameters = shift;
my ( $dfs_access, $branch, $language, $build_id,
$platform, $sku ) =
GetNamedParameters( $named_parameters,
q(DfsMap), q(Branch), q(Language), q(BuildID),
q(Platform), q(Sku) );
return if ( !defined $dfs_access );
# Get writeable DFS share for our branch
my @writeable_dfs_shares = GetWriteableDfsShares( $named_parameters );
if ( !@writeable_dfs_shares ) {
dbgmsg( "Unknown writeable DFS share for $branch." );
return;
}
my $dfs_link = GetDfsLinkName( $named_parameters );
if ( !defined $dfs_link ) {
errmsg( "Do not know how to create $build_id DFS link for $branch / $sku." );
return;
}
# Want the path up to platform: main\usa\<build_id>\<platform>
$dfs_link =~ s/^(.*$platform)\\.*$/$1/;
# All known branches use the same writeable share format
if ( $branch ) # TRUE
{
return map {"$_\\$dfs_link"} @writeable_dfs_shares;
}
# Unknown branch
else {
return;
}
}
sub ModifyQlyFile {
my $build = shift;
my $quality = shift;
my $path_to_quality_file = shift;
my ( @existing_qlyfiles, $qly_file );
my ( $fqlyfile_already_set );
# Check for existing QLY files
@existing_qlyfiles = grep { $_ if ( ! -d $_ ) } globex "$path_to_quality_file\\*.qly";
# 0 = success
# 2 = couldn't find file (may be a failure or there may be no QLY file;
# have to treat as a success as there is no way
# to tell the difference - $^E doesn't help)
if ( $! != 0 && $! != 2 ) {
errmsg( "Trying to find existing QLY file: $!" );
return;
}
# Remove any QLY files that don't match current status
# -- there should never be more than one, but we
# should handle that case correctly
undef $fqlyfile_already_set;
foreach $qly_file ( @existing_qlyfiles ) {
my ($qly_file_quality) = $qly_file =~ /\\([^\\]+)\.qly$/;
if ( lc $qly_file_quality eq lc $quality ) {
# Check for correct information inside
my $qly_information = ReadQlyFile( $qly_file );
if ( !defined $qly_information ) {
wrnmsg( "Problem reading $qly_file" );
}
elsif ( $qly_information =~ /^$build$/i ) {
$fqlyfile_already_set = 1;
next;
}
}
elsif ( !AllowQualityTransition( $qly_file_quality, $quality ) ) {
errmsg( "Not allowed to go from '$qly_file_quality' to '$quality' quality!" );
return;
}
# Remove the QLY file
unless ( unlink $qly_file ) {
errmsg( "Could not delete $qly_file ($!)" );
return;
}
}
# Create new QLY file if necessary
if ( !$fqlyfile_already_set ) {
unless ( open QLYFILE, "> $path_to_quality_file\\$quality.qly" ) {
errmsg( "Could not write to $path_to_quality_file\\$quality.qly ($!)" );
return;
}
print QLYFILE "$build\n";
close QLYFILE;
}
return 1;
}
sub ReadQlyFile {
my $qlyfile_path = shift;
my ( $qlyfile_contents );
unless ( open QLYFILE, $qlyfile_path ) {
errmsg( "Failure opening $qlyfile_path ($!)" );
return;
}
$qlyfile_contents = <QLYFILE>;
close QLYFILE;
chomp $qlyfile_contents;
return $qlyfile_contents;
}
sub DeleteQlyFile {
my $path_to_quality_file = shift;
my @existing_qlyfiles;
# Check for existing QLY files
@existing_qlyfiles = grep { $_ if ( ! -d $_ ) } globex "$path_to_quality_file\\*.qly";
# 0 = success
# 2 = couldn't find file (may be a failure or there may be no QLY file;
# have to treat as a success as there is no way
# to tell the difference - $^E doesn't help)
if ( $! != 0 && $! != 2 ) {
errmsg( "Trying to find existing QLY file: $!" );
return;
}
my $qly_file;
foreach $qly_file ( @existing_qlyfiles ) {
if ( !unlink $qly_file ) {
wrnmsg( "Unable to remove $qly_file ($!)" );
}
}
return 1;
}
sub CreateSecuredBuildShare {
my $named_parameters = shift;
my ( $accessed_shares, $full_build_path, $computer_name,
$quality, $build_number, $language,
$platform, $branch, $sku ) =
GetNamedParameters( $named_parameters,
q(AccessedShares), q(FullBuildPath), q(ComputerName),
q(Quality), q(BuildNumber), q(Language),
q(Platform), q(Branch), q(Sku) );
return if ( !defined $accessed_shares );
my $fshare_already_exists;
my ( @share_members, @remove_permissions );
my ( $build_share, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $build_share ) {
errmsg( "Unable to determine release share for $branch / $sku." );
return;
}
# Optimization: check if we have already looked at this share
# (especially useful when each sku uses the same share)
foreach ( @$accessed_shares ) {
return 1 if ( lc $_ eq lc $build_share );
}
# Choose share permissions based on quality
if ( lc $quality eq "pre" || lc $quality eq "bvt" ) {
my $share_member_string = &GetIniSetting::GetSettingQuietEx( $branch, $language, "BVTMembers" );
if ( $share_member_string ) {
@share_members = split ' ', $share_member_string;
}
# INI file must specify permissions
else {
errmsg( "No entries specified in INI file for BVTMembers" );
return;
}
} else {
my $share_member_string = &GetIniSetting::GetSettingQuietEx( $branch, $language, "ReleaseAccess" );
if ( $share_member_string ) {
@share_members = split ' ', $share_member_string;
}
# INI file must specify permissions
else {
errmsg( "No entries specified in INI file for ReleaseAccess" );
return;
}
}
# Create share if it doesn't already exist
$fshare_already_exists = ( -e $build_share );
# This will set the current permissions and create the share if necessary
dbgmsg( "Setting share permissions for $build_share:" );
dbgmsg( " $_" ) foreach @share_members;
if ( !ExecuteProgram::ExecuteProgram( "rmtshare.exe $build_share". ($fshare_already_exists?" ":"=$full_build_path ").
join " ", map { "/GRANT $_:R" } @share_members) ) {
errmsg( "Failed creating/modifying share for $full_build_path." );
errmsg( " ". ExecuteProgram::GetLastCommand() );
errmsg( " $_" ) foreach ExecuteProgram::GetLastOutput();
return;
}
# Remove any old permissions from an existing share
# We don't want to drop the share and recreate it
# because current connections will be dropped and
# we decided that was bad
if ( $fshare_already_exists ) {
my %hash_share_members;
$hash_share_members{ lc $_ } = 1 foreach @share_members;
if ( !ExecuteProgram::ExecuteProgram("rmtshare.exe $build_share") ) {
errmsg( "Failure executing ". ExecuteProgram::GetLastCommand() );
errmsg ( " $_" ) foreach ExecuteProgram::GetLastOutput();
return;
}
my @current_share_permissions = ExecuteProgram::GetLastOutput();
undef @remove_permissions;
my $permission;
my $interesting_lines;
foreach $permission ( @current_share_permissions ) {
my $user_name;
# skip empty lines
next if ( $permission eq "" );
# Deal with no permissions set
if ( $permission eq "No permission specified." ) {
wrnmsg( "Share $build_share has no default permissions." );
last;
}
# Otherwise we only care about the lines between
# 'Permissions:' and 'The command completed successfully.'
if ( $permission eq 'Permissions:' ) {
$interesting_lines = 1;
next;
}
last if ( $permission eq 'The command completed successfully.' );
next if ( !$interesting_lines );
# Only need to verify name and not permissions as rmtshare should
# have already set the correct permissions for the name.
# Note that rmtshare also prepends a backslash for builtin
# groups, but you cannot use the backslash when specifying
# the name on the command line, so strip out any
# prepending backslash characters. This also catches
# local groups as they are reported as <machine>\Group on
# remote machines, but can only be added and removed as 'Group".
($user_name) = $permission =~ /^\s*\\?(?:$computer_name\\)?(\S+)\s*\:/i;
if ( !$user_name ) {
errmsg( "Problem parsing user from rmtshare output:" );
errmsg( " $permission" );
return;
}
if ( !exists $hash_share_members{ lc $user_name } ) {
push @remove_permissions, $user_name;
}
}
# Now actually call rmtshare to remove unrecognized users
if ( @remove_permissions ) {
dbgmsg( "Removing permissions from $build_share:" );
dbgmsg( " $_" ) foreach @remove_permissions;
if ( !ExecuteProgram::ExecuteProgram( "rmtshare.exe $build_share ". join " ", map { "/REMOVE $_" } @remove_permissions ) ) {
errmsg( "Failed removing old permissions from $build_share." );
errmsg ( " ". ExecuteProgram::GetLastCommand() );
errmsg ( " $_" ) foreach ExecuteProgram::GetLastOutput();
return;
}
}
}
# Verify that share is reachable
# This is making the assumption that we
# are allowed access to the share
if ( ! -e $build_share ) {
errmsg( "Unable to access $build_share ($!)" );
return;
}
# Add to accessed shares array
push @$accessed_shares, $build_share;
return 1;
}
sub LowerBuildShare {
my $named_parameters = shift;
my ( $accessed_shares, $branch, $sku, $lang ) =
GetNamedParameters( $named_parameters,
q(AccessedShares), q(Branch), q(Sku), q(Language) );
my ( $build_share, $sub_path ) = GetShareName( $named_parameters );
if ( !defined $build_share) {
errmsg( "Unable to determine release share for $branch / $sku." );
return;
}
# Optimization: check if we have already removed this share
# (especially useful when each sku uses the same share)
foreach ( @$accessed_shares ) {
return 1 if ( lc $_ eq lc $build_share );
}
# Check if the share exists
my $fshare_exists = ( -e $build_share );
# Remove the share
if ( $fshare_exists ) {
if ( !ExecuteProgram::ExecuteProgram( "rmtshare.exe $build_share /DELETE" ) ) {
errmsg( "Failed removing share $build_share." );
errmsg( " ". ExecuteProgram::GetLastCommand() );
errmsg( " $_") foreach ExecuteProgram::GetLastOutput();
return;
}
}
# If the share doesn't exist we are done,
# but do a sanity check on the reason
elsif ( $! != 2 ) {
errmsg( "Unable to verify share $build_share ($!)." );
return;
}
# Add to accessed shares array
push @$accessed_shares, $build_share;
return 1;
}
sub GetReleasePath {
my( @net_share_output, @net_share_local_path_entry );
my( $field_name, $field_value );
if ( !ExecuteProgram::ExecuteProgram("net share release") ) {
errmsg( "Unable to find share \'release\'.");
errmsg( " ". ExecuteProgram::GetLastCommand() );
errmsg( " $_" ) foreach ExecuteProgram::GetLastOutput();
return;
}
@net_share_output = ExecuteProgram::GetLastOutput();
# note that we are using the second line of the net share
# output to get this data. if this changes, it'll break.
@net_share_local_path_entry = split /\s+/, $net_share_output[1];
dbgmsg( "Path information for release is '". (join " ", @net_share_local_path_entry). "'" );
if ( $net_share_local_path_entry[0] ne "Path" ) {
wrnmsg( "Second line of net share does not start with " .
"\'Path\' -- possibly another language?");
}
dbgmsg( "Release path is $net_share_local_path_entry[1]");
return $net_share_local_path_entry[1];
}
#
# glob does not work for UNC
# paths on Win64 so we have to
# fake it with dir /b
#
sub globex ($)
{
my $match_criteria = shift;
croak "Must specify parameter for globex" if ( !defined $match_criteria );
# If we are on an Win64 version of perl check for UNC path
if ( $^O eq 'MSWin64' &&
$match_criteria =~ /^\\\\/ ) {
my ($path) = $match_criteria =~ /(.*)\\[^\\]*$/;
my @dir_entries = `dir /b $match_criteria`;
if ( $? ) {
# Can't do much useful in case of an error
return;
}
# Need to clean the entries up to look like glob results
@dir_entries = grep { $_ } @dir_entries; # no empty results
chomp @dir_entries; # no newlines in results
# full path to results
if ( defined $path ) {
foreach ( @dir_entries ) { $_ = "$path\\$_"; }
}
return @dir_entries;
}
# otherwise just use glob
else {
return glob( $match_criteria );
}
}
parseargs('?' => \&Usage,
'lower' => sub { $Op = 'lower' },
'n:' => \$Build_Number,
'q:' => \&ValidateAndSetQuality,
'a:' => \$Architecture,
't:' => \$Debug_Type,
'time:' => \$Time_Stamp,
'f' => \$fOverride_Safe,
'safe' => \$fSafe,
'replace' => \$fReplaceAtNumberedLink,
'd' => \$Logmsg::DEBUG);
# 'l:' => \$Language, # Done by ParseEnv
Usage() if ( @ARGV );
$Language = $ENV{LANG};
# Initialize with default values if not specified
$Op ||= 'raise';
$Build_Number ||= '*';
$Architecture ||= '*';
$Time_Stamp ||= '*';
$Debug_Type ||= '*';
$Platform = "$Architecture$Debug_Type";
if ( !Main() ) {
errmsg( "Terminated abnormally." );
1;
}
else {
logmsg( "Completed successfully." );
0;
}
sub ValidateAndSetQuality {
my $quality = shift;
if ( exists $Ordered_Qualities{ lc $quality } ) {
$Quality = $quality;
return 1;
}
else {
errmsg( "Unrecognized quality \'$quality\'" );
exit( 1 );
}
}
sub Usage {
my $name = $0;
print "$name\n";
$name =~ s/.*?([^\\]+)$/$1/;
print <<USAGE;
$name -n:\# -q:qly [-lower] [-a:arc] [-l:lng] [-d] [-?]
-n:# Raise build number #
-q:qly Raise to 'qly' quality
-lower Lower a build and remove from dfs
-a:arc Act on architecture 'arc' (e.g. ??????)
-t:typ Act on debug type 'typ' (e.g. fre or chk)
-time:yymmdd-hhmm Use time-stamp criteria
-l:lng Raise for language 'lng' (from codes.txt)
-safe Used to raise an old build to TST/IDx (RAISE only)
-replace Will replace "newer" builds at a numbered link
-f Force an action that would not normally be taken
-d Debug mode
-? Display usage (this screen)
$name will issue the appropriate DFS commands to raise
a build to a specific quality.
USAGE
exit(1);
}
package ExecuteProgram;
my $Last_Command;
my @Last_Output;
sub ExecuteProgram {
$Last_Command = shift;
@Last_Output = `$Last_Command`;
chomp $_ foreach ( @Last_Output );
if ( $? ) {
return;
}
else {
return 1;
}
}
sub GetLastCommand {
return $Last_Command;
}
sub GetLastOutput {
return @Last_Output;
}
# Object to control synchronization among machines
package MultiMachineLock;
use Win32::ChangeNotify;
use Win32::Event;
sub new {
my $class = shift;
my $spool_path = shift;
my $timeout = shift;
return if not defined $spool_path;
my $instance = {
Path => $spool_path,
Timeout => $timeout || INFINITE, # default timeout is INFINITE
File => "$ENV{COMPUTERNAME}.txt",
NotificationObj => undef,
LocalSyncObj => undef,
fHaveLocalSync => 0,
Count => 0,
LastErrorMsg => "",
LastErrorValue => 0
};
return bless $instance;
}
sub Lock {
my $self = shift;
my $wait_return;
return unless ref $self;
# If we already hold the lock just bump up the ref-count
if ( 0 != $self->{Count} ) {
$self->{Count}++;
return 1;
}
# Need to synchronize local access (only one program
# at a time from the same machine can hold the lock)
if ( !defined $self->{LocalSyncObj} ) {
my $lock_path = $self->{Path};
$lock_path =~ s/\\\\/remote\\/;
$lock_path =~ s/\\/\//g;
$self->{LocalSyncObj} = new Win32::Event ( 0, 1, "Global\\$lock_path" );
if ( !defined $self->{LocalSyncObj} ) {
$self->{LastErrorMsg} = "Unable to create a local event ($^E).";
$self->{LastErrorValue} = 1;
return;
}
}
if ( !$self->{fHaveLocalSync} ) {
$wait_return = $self->{LocalSyncObj}->wait( $self->{Timeout} );
# Handle timeout
if ( 0 == $wait_return ) {
$self->{LastErrorMsg} = "Waiting on another process on machine.";
$self->{LastErrorValue} = 2; # 2 = timeout
return;
}
# Handle error condition
elsif ( 1 != $wait_return ) {
$self->{LastErrorMsg} = "Unexpected error waiting for local synchronization (wait returned ". $wait_return. ")";
$self->{LastErrorValue} = 1;
return;
}
$self->{fHaveLocalSync} = 1;
}
# Check for our existence before
# we create a file in the queue
# (this will be the case if something bad
# happened on the last run and we are
# rerunning the command to recover)
if ( ! -e "$self->{Path}\\$self->{File}" ) {
unless ( open SPOOLFILE, "> $self->{Path}\\$self->{File}" ) {
$self->{LastErrorMsg} = "Failed to create synchronization file '$self->{Path}\\$self->{File}' ($!).";
$self->{LastErrorValue} = 1;
return;
}
print SPOOLFILE "This file is used to synchronize access among machines.";
close SPOOLFILE;
}
# Create the notification object if this is the first time we were called
$self->{NotificationObj} ||= new Win32::ChangeNotify( $self->{Path}, 0, FILE_NOTIFY_CHANGE_FILE_NAME );
if ( !defined ${$self->{NotificationObj}} ) {
$self->{LastErrorMsg} = "Failed creating monitor object ($^E).";
$self->{LastErrorValue} = 1;
return;
}
# Check our position in the 'queue' at the beginning
if ( !ExecuteProgram::ExecuteProgram("dir /b /od $self->{Path}") ) {
$self->{LastErrorMsg} = "Unable to view directory $self->{Path}.\n";
$self->{LastErrorMsg} .= " ". ExecuteProgram::GetLastCommand(). "\n";
$self->{LastErrorMsg} .= " $_\n" foreach ExecuteProgram::GetLastOutput();
$self->{LastErrorValue} = 1;
return;
}
my @ordered_spool_directory = ExecuteProgram::GetLastOutput();
# We are at the top
if ( $ordered_spool_directory[0] eq $self->{File} ) {
$self->{Count}++;
return 1;
}
# Recheck our position in the 'queue' everytime
# the state of the directory changes until we
# are in the top position or we hit the timeout
# value with no activity
while ( 1 ) {
$self->{NotificationObj}->reset();
$wait_return = $self->{NotificationObj}->wait( $self->{Timeout} );
if ( !ExecuteProgram::ExecuteProgram("dir /b /od $self->{Path}") ) {
$self->{LastErrorMsg} = "Unable to view directory $self->{Path}.\n";
$self->{LastErrorMsg} .= " ". ExecuteProgram::GetLastCommand(). "\n";
$self->{LastErrorMsg} .= " $_\n" foreach ExecuteProgram::GetLastOutput();
$self->{LastErrorValue} = 1;
return;
}
@ordered_spool_directory = ExecuteProgram::GetLastOutput();
# We are at the top
if ( $ordered_spool_directory[0] eq $self->{File} ) {
$self->{Count}++;
return 1;
}
# Still waiting for others
else {
# Make sure we are still in the queue
my $found_self;
foreach ( @ordered_spool_directory ) {
$found_self = 1 if ( $_ eq "$self->{File}" );
}
if ( !$found_self ) {
# Lost our synchronization file -- recreating...
unless ( open SPOOLFILE, "> $self->{Path}\\$self->{File}" ) {
$self->{LastErrorMsg} = "Lost our synchronization file '$self->{Path}\\$self->{File}' -- failed to recreate. ($!).";
$self->{LastErrorValue} = 1;
return;
}
print SPOOLFILE "This file is used to synchronize access among machines.";
close SPOOLFILE;
}
}
# Handle timeout
if ( 0 == $wait_return ) {
my $machine_name = $ordered_spool_directory[0];
$machine_name =~ s/\.txt$//;
$self->{LastErrorMsg} = "Waiting on $machine_name ($self->{Path}\\$ordered_spool_directory[0]).";
$self->{LastErrorValue} = 2; # 2 = timeout
return;
}
# Handle error condition
elsif ( 1 != $wait_return ) {
$self->{LastErrorMsg} = "Unexpected error waiting on directory to change (wait returned ". $wait_return. ")";
$self->{LastErrorValue} = 1;
return;
}
}
$self->{LastErrorMsg} = "Jumped out of an infinte loop -- no idea how we got here";
$self->{LastErrorValue} = 1;
return;
}
sub Unlock {
my $self = shift;
return unless ref $self;
# Can't release a lock we don't hold
if ( 0 == $self->{Count} ) {
return;
}
# Decrement lock count
if ( 0 == --$self->{Count} ) {
if ( ! unlink "$self->{Path}\\$self->{File}" ) {
$self->{LastErrorMsg} = "Failed to delete synchronization file ($self->{Path}\\$self->{File}). May need to delete by hand.";
$self->{LastErrorValue} = 1;
}
$self->{LocalSyncObj}->set();
}
return 1;
}
sub GetLastError {
my $self = shift;
return unless ref $self;
return $self->{LastErrorValue};
}
sub GetLastErrorMessage {
my $self = shift;
return unless ref $self;
return $self->{LastErrorMsg};
}
sub DESTROY {
my $self = shift;
return unless ref $self;
if ( $self->{Count} > 0 ) {
#print "Abandoning lock file -- attempting to delete.";
$self->{Count} = 1;
return $self->Unlock();
} else {
if ( $self->{fHaveLocalSync} ) {
$self->{LocalSyncObj}->set();
}
return 1;
}
}
package StaticDfsMap;
my $Last_Error_Msg;
sub TIEHASH {
my $class = shift;
my $dfs_root = shift;
my $instance;
my ($dfs_view_info, $cur_dfs_link);
my $fchild_dfs_servers;
return if ( @_ or !defined $dfs_root );
# Check current dfscmd.exe version
if ( !VerifyDfsCmdVer() ) {
return; # Error message should already be set
}
# Remove trailing backslash (if it exists) from DFS root
$dfs_root =~ s/\\$//;
# Read in current DFS view
if ( !ExecuteProgram::ExecuteProgram("dfscmd.exe /view $dfs_root /batch") ) {
$Last_Error_Msg = "Failed viewing dfs root $dfs_root.\n";
$Last_Error_Msg .= " ". ExecuteProgram::GetLastCommand(). "\n";
$Last_Error_Msg .= " $_\n" foreach ExecuteProgram::GetLastOutput();
return;
}
my @full_dfs_view = ExecuteProgram::GetLastOutput();
$instance = {
"DFS Root" => $dfs_root };
# "DFS Work Root" => {}
# "DFS Servers" => (),
# "Map" => {}
# Expected output:
# dfscmd /map "\\<dfs_root>\<link_name>" "\\<server>\<share>" "<comment>"
my %dfs_map;
foreach my $dfs_view_info ( @full_dfs_view ) {
chomp $dfs_view_info;
next if ( !$dfs_view_info );
next if ( $dfs_view_info =~ /^REM BATCH RESTORE SCRIPT/ );
$dfs_view_info = lc $dfs_view_info;
# Are we done?
if ( $dfs_view_info eq 'the command completed successfully.' ) {
last;
}
my ($dfs_link, $share, $comment) = $dfs_view_info =~ /dfscmd \/(?:map|add) "(.*?)" "(.*?)"(?: "(.*)")?$/;
my $relative_link_name = $dfs_link;
$relative_link_name =~ s/^\Q$dfs_root\E\\//i;
if ( !$dfs_link || !$share || !$relative_link_name )
{
$Last_Error_Msg = "Unrecognized entry parsing ". ExecuteProgram::GetLastCommand(). "\n";
$Last_Error_Msg .= " $dfs_view_info";
return;
}
# If it is just the root, then this entry represents a hosting server
if ( lc $dfs_link eq lc $dfs_root ) {
push @{$instance->{"DFS Servers"}}, $share;
}
# Otherwise associate the link and the share
else {
push @{$dfs_map{$relative_link_name}}, $share;
push @{$dfs_map{$share}}, $relative_link_name;
}
}
# Expect to find at least one DFS server under the root
if ( !exists $instance->{"DFS Servers"} ) {
$Last_Error_Msg = "Did not find the root node and specified servers for $dfs_root.";
return;
}
# FUTURE: find a nicer way around this
# In cases where more than one server is actually hosting DFS,
# we need to pick one of the hosting machines to perform
# our commands against so that we don't have to wait for
# replication (this is to help tests for just created and
# just removed directories succeed).
foreach ( @{$instance->{"DFS Servers"}} ) {
if ( -e $_ ) {
$instance->{"DFS Work Root"} = $_;
last;
}
}
# Verify we could see at least one
if ( ! exists $instance->{"DFS Work Root"} ) {
$Last_Error_Msg = "Could not find an accessible DFS server.";
return;
}
#print "\n$_:\n ". (join "\n ", @{$dfs_map{$_}}) foreach ( sort keys %dfs_map );
# Store the map we just created
# inside of our private hash data
$instance->{"Map"} = \%dfs_map;
return bless $instance, $class;
}
sub FETCH {
my $self = shift;
my $link_or_share = shift;
my $dfs_map = $self->{"Map"};
return $dfs_map->{ lc $link_or_share } if (exists $dfs_map->{ lc $link_or_share });
return;
}
sub STORE {
my $self = shift;
my $link_or_share = shift;
my $new_link = shift;
my $dfs_map = $self->{"Map"};
my $fshare = $link_or_share =~ /^\\\\/;
# Remove any trailing backslashes
$link_or_share =~ s/\\$//;
$new_link =~ s/\\$//;
# If mapping already exists, we are done
my $cur_mappings = $dfs_map->{lc $link_or_share};
foreach ( @$cur_mappings ) {
return 1 if ( lc $_ eq lc $new_link );
}
# Create new DFS mapping
if ( !ExecuteProgram::ExecuteProgram(
"dfscmd.exe ". (@$cur_mappings?"/add ":"/map "). $self->{"DFS Root"}.
($fshare?"\\$new_link $link_or_share":"\\$link_or_share $new_link") )
) {
#
# COMMENTED OUT: If someone else is trying to manipulate the same
# links as we are while we are doing this then
# we have a problem -- don't try to correct
#
# Might have gotten into a race-condition with another
# machine attempting to add a new link -- if that is
# a possibility try a map instead before giving up
#
#if ( !@$cur_mappings ||
# !ExecuteProgram( "dfscmd.exe /map ". $self->{"DFS Root"}.
# ($fshare?"\\$new_link $link_or_share":"\\$link_or_share $new_link") ) ) {
# errmsg( "Failed to create link \\$self->{Dfs Root}\\". ($fshare?"\\$new_link => $link_or_share":"\\$link_or_share => $new_link") . "." );
# return;
#}
$Last_Error_Msg = "Problem adding/mapping link ". ($fshare?"$link_or_share":"$new_link"). ":\n";
$Last_Error_Msg .= " " . ExecuteProgram::GetLastCommand() . "\n";
$Last_Error_Msg .= join "\n ", ExecuteProgram::GetLastOutput();
return;
}
# Update the hash with the new information
push @{$dfs_map->{lc $link_or_share}}, $new_link;
push @{$dfs_map->{lc $new_link}}, $link_or_share;
return 1;
}
sub DELETE {
my $self = shift;
my $link_or_share = shift;
my $dfs_map = $self->{"Map"};
my $fshare = $link_or_share =~ /^\\\\/;
# Make sure DFS mapping exists before attempting to delete it
return 1 if ( !exists $dfs_map->{lc $link_or_share} );
my $cur_mappings = $dfs_map->{lc $link_or_share};
return 1 if ( !@$cur_mappings );
if ( $fshare ) {
my $next_link;
# Remove all DFS links pointing to this share
foreach $next_link ( @$cur_mappings ) {
if ( !$self->RemoveShareFromLink( $next_link, $link_or_share ) ) {
return;
}
}
} else {
if ( !ExecuteProgram::ExecuteProgram(
"dfscmd.exe /unmap ". $self->{"DFS Root"}. "\\$link_or_share"
) ) {
$Last_Error_Msg = "Failure trying to unmap $link_or_share:\n";
$Last_Error_Msg .= " " . ExecuteProgram::GetLastCommand() . "\n";
$Last_Error_Msg .= join "\n ", ExecuteProgram::GetLastOutput();
return;
}
# Remove the hash references
delete $dfs_map->{lc $link_or_share};
for ( my $i = 0; $i < scalar(@$cur_mappings); $i++) {
if ( lc $cur_mappings->[$i] eq lc $link_or_share ) {
splice @$cur_mappings, $i, 1;
}
}
}
return 1;
}
sub CLEAR {
my $self = shift;
# We don't actually want to be able to delete the entire
# DFS structure, so we will just clear our information
undef %$self;
return;
}
sub EXISTS {
my $self = shift;
my $link_or_share = shift;
my $dfs_map = $self->{"Map"};
return exists $dfs_map->{lc $link_or_share};
}
sub FIRSTKEY {
my $self = shift;
my $dfs_map = $self->{"Map"};
my $force_to_first_key = keys %$dfs_map;
return scalar each %$dfs_map;
}
sub NEXTKEY {
my $self = shift;
my $last_key = shift;
my $dfs_map = $self->{"Map"};
return scalar each %$dfs_map;
}
sub DESTROY {
my $self = shift;
return;
}
#
# Need more methods than provided by TIEHASH defaults
#
sub RemoveShareFromLink {
my $self = shift;
my $link_root = shift;
my $share_name = shift;
my $dfs_map = $self->{"Map"};
# Get current associated links
my $cur_link_mappings = $dfs_map->{lc $link_root};
my $cur_share_mappings = $dfs_map->{lc $share_name};
my $fshare_linked;
# If the share is not part of the link, return
foreach (@$cur_link_mappings) {
if ( lc $_ eq lc $share_name ) {
$fshare_linked = 1;
last;
}
}
return 1 if ( !$fshare_linked );
my $dfs_command = "dfscmd.exe /remove ".
$self->{'DFS Root'}.
"\\$link_root $share_name";
if ( !ExecuteProgram::ExecuteProgram( $dfs_command ) ) {
$Last_Error_Msg = "Could not remove $share_name from DFS link $link_root:\n";
$Last_Error_Msg .= " " . ExecuteProgram::GetLastCommand() . "\n";
$Last_Error_Msg .= join "\n ", ExecuteProgram::GetLastOutput();
return;
}
# Remove the associated hash entries
for ( my $i = 0; $i < scalar(@$cur_link_mappings); $i++) {
if ( lc $cur_link_mappings->[$i] eq lc $share_name ) {
splice @$cur_link_mappings, $i, 1;
}
}
for ( my $i = 0; $i < scalar(@$cur_share_mappings); $i++) {
if ( lc $cur_share_mappings->[$i] eq lc $link_root ) {
splice @$cur_share_mappings, $i, 1;
}
}
return 1;
}
sub Flush {
my $self = shift;
# Do nothing for now, but here in preparation for batching of commands
return 1;
}
sub GetDfsRoot {
my $self = shift;
# return $self->{ "DFS Root" };
return $self->{ "DFS Root" };
}
sub GetDfsHosts {
my $self = shift;
return @{$self->{"DFS Servers"}};
}
sub VerifyDfsCmdVer {
my ( @file_spec, $file_ver_string, @file_ver_info );
# Verify the dfscmd.exe is proper
if ( !ExecuteProgram::ExecuteProgram("where dfscmd.exe") ) {
$Last_Error_Msg = "Could not find dfscmd.exe\n";
$Last_Error_Msg .= " ". ExecuteProgram::GetLastCommand(). "\n";
$Last_Error_Msg .= " $_\n" foreach ExecuteProgram::GetLastOutput();
return;
}
@file_spec = ExecuteProgram::GetLastOutput();
if ( !ExecuteProgram::ExecuteProgram("filever $file_spec[0]") ) {
$Last_Error_Msg = "Failed executing \'filever $file_spec[0]\'.";
return;
}
$file_ver_string = join " ", ExecuteProgram::GetLastOutput();
@file_ver_info = split /\s+/, $file_ver_string;
# Fourth field is version number; compare it with known good
if ($file_ver_info[4] lt "5.0.2203.1") {
$Last_Error_Msg = "$file_ver_info[0] version is less than 5.0.2203.1. Please install DFSCMD.EXE from a 2203 or later build.";
return;
}
return 1;
}
sub GetLastErrorMessage {
return $Last_Error_Msg;
}