#!/usr/bin/perl # # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is Patcher 2, a patch generator for the AUS2 system. # # The Initial Developer of the Original Code is # Mozilla Corporation # # Portions created by the Initial Developer are Copyright (C) 2006 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Chase Phillips (chase@mozilla.org) # J. Paul Reed (preed@mozilla.com) # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** # use strict; use Getopt::Long; use Data::Dumper; use Cwd; use English; use IO::Handle; use File::Path; use File::Copy qw(move copy); use MozAUSConfig; use MozAUSLib qw(CreatePartialMarFile GetAUS2PlatformStrings ValidateToolsDirectory MkdirWithPath SubstitutePath RunShellCommand); $Data::Dumper::Indent = 1; autoflush STDOUT 1; autoflush STDERR 1; ## ## CONSTANTS ## use vars qw($PID_FILE $DEFAULT_HASH_TYPE $DEFAULT_CVSROOT $DEFAULT_SCHEMA_VERSION $CURRENT_SCHEMA_VERSION); $PID_FILE = 'patcher2.pid'; $DEFAULT_HASH_TYPE = 'SHA1'; $DEFAULT_CVSROOT = ':pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot'; $DEFAULT_SCHEMA_VERSION = 0; $CURRENT_SCHEMA_VERSION = 1; sub main { Startup(); my (%args, %move_args); my $config = new MozAUSConfig(); PrintUsage(exitCode => 1) if ($config eq undef); if (not $config->RequestedStep('build-tools') and not ValidateToolsDirectory(toolsDir => $config->GetToolsDir())) { my $badDir = $config->GetToolsDir(); print STDERR <<__END_TOOLS_ERROR__; ERROR: $badDir doesn't contain the required build tools and --build-tools wasn't requested; bailing... __END_TOOLS_ERROR__ PrintUsage(exitCode => 1); } my $startdir = getcwd(); my $temp_prefix = lc($config->GetApp()); MkdirWithPath("temp/$temp_prefix") or die "ASSERT: MkdirWithPath(temp/$temp_prefix) FAILED\n"; #printf("PRE-REMOVE-BROKEN-UPDATES:\n\n%s", Data::Dumper::Dumper($config)); $config->RemoveBrokenUpdates(); #printf("POST-REMOVE-BROKEN:\n\n%s", Data::Dumper::Dumper($config)); BuildTools(config => $config) if $config->RequestedStep('build-tools'); run_download_complete_patches(config => $config) if $config->RequestedStep('download'); if ($config->RequestedStep('create-patches')) { CreatePartialPatches(config => $config); CreateCompletePatches(config => $config); } if ($config->RequestedStep('(create-patches|create-patchinfo)')) { CreatePartialPatchinfo(config => $config); CreateCompletePatchinfo(config => $config); CreatePastReleasePatchinfo(config => $config); } if ($config->RequestedStep('move-update')) { MoveUpdate(config => $config, from => $move_args{'from'}, to => $move_args{'to'}); } Shutdown(); } ################# sub PrintUsage { my %args = @_; my $exitCode = $args{'exitCode'}; print STDERR <<__END_USAGE__; You screwed up this command usage; oh well; no docs yet. __END_USAGE__ exit($exitCode) if (defined($exitCode)); } sub BuildTools { my %args = @_; my $config = $args{'config'}; my $codir = $config->GetToolsDir(); my $startdir = getcwd(); # Handle the cases where we shouldn't/can't proceed. if ( -e $codir and ! -d $codir ) { die "ERROR: $codir exists and isn't a directory"; } # Make the parent path. MkdirWithPath($codir, 0, 0751) or die "ERROR: MkdirWithPath($codir) FAILED"; chdir($codir); # Handle the cases where we shouldn't/can't proceed. if (-e "$codir/mozilla") { die "ERROR: $codir/mozilla exists. Please move it away before continuing!"; } { # Create and execute CVS checkout command. printf("Checking out source code... \n"); my $cvsroot = $ENV{'CVSROOT'} || $DEFAULT_CVSROOT; my $checkout = "cvs -d $cvsroot co mozilla/client.mk"; my $rv = RunShellCommand(command => $checkout, output => 1); if ($rv->{'exit_value'} != 0) { print "$checkout FAILED: $rv->{'output'}\n"; } # TODO - fix this to refer to the update-tools pull-target when # bug 329686 gets fixed. my $makeCheckout = 'MOZ_CO_PROJECT=all '; $makeCheckout .= 'make -f mozilla/client.mk checkout'; $rv = RunShellCommand(command => $makeCheckout, output => 1); if ($rv->{'exit_value'} != 0) { print "$makeCheckout FAILED: $rv->{'output'}\n"; } printf("\n\nCheckout complete.\n"); } # Create and execute CVS checkout command. # The checkout directory should exist but doesn't. if (not -e "$codir/mozilla") { die "ERROR: Couldn't create checkout directory $codir/mozilla"; } # Change directory to the child directory. chdir("$codir/mozilla") or die "chdir into $codir/mozilla FAILED: $!"; { # Build mozilla dependencies and tools. my $mozconfig; $mozconfig = "mk_add_options MOZ_CO_PROJECT=tools/update-packaging\n"; $mozconfig .= "ac_add_options --enable-application=tools/update-packaging\n"; # This is necessary because PANGO'S NOW A DEPENDANCY! WHEEEEEE. # (but update packaging doesn't need it) $mozconfig .= "ac_add_options --enable-default-toolkit=gtk2\n"; open(MOZCFG, '>.mozconfig') or die "ERROR: Opening .mozconfig for writing failed: $!"; print MOZCFG $mozconfig; close(MOZCFG); my $rv = RunShellCommand(command => './configure', output => 1); if ($rv->{'exit_value'} != 0) { print "./configure FAILED: $rv->{'output'}\n"; } $rv = RunShellCommand(command => 'make', output => 1); if ($rv->{'exit_value'} != 0) { print "make FAILED: $rv->{'output'}\n"; } } # Build mozilla dependencies and tools. if (not ValidateToolsDirectory(toolsDir => $codir)) { die "BuildTools(): Couldn't find the tools after a BuildTools() run; something's wrong... bailing...\n"; } # Change directory to the starting directory. chdir($startdir); return 1; } sub hash_file { my %args = @_; my %hash_size_map = ( SHA512 => "H128", MD5 => "H128", SHA1 => "H128", ); my($file, $hash_type, $bltest_hash_type, $hash_size); $file = $args{'file'}; $hash_type = $args{'hash_type'}; $bltest_hash_type = lc($hash_type); $hash_type = $DEFAULT_HASH_TYPE if !defined($hash_type); my $hash_size = $hash_size_map{$hash_type}; ## ## TODO - This should be reverted back to what it was once bug 329686 ## lands and gives us a way to build NSS standalone to get the bltest ## utility (which is what was used before). ## if ($hash_type eq 'SHA512') { die "ASSERT: SHA512 not ACTUALLY supported\n"; } elsif ($hash_type eq 'MD5' || $hash_type eq 'SHA1') { $hash_type = lc($hash_type); } else { die "ASSERT: Unknown hash type requested: $hash_type\n"; } die "ASSERT: File $file doesn't exist" if (not -r $file); my $hexstring_hash = `openssl dgst -$hash_type $file`; $? and die("$hexstring_hash of $file FAILED: $!"); # Expects input like MD5(mozconfig)= d7433cc4204b4f3c65d836fe483fa575 # Removes everything up to and including the "= " $hexstring_hash =~ s/^.+\s+(\w+)$/$1/; chomp($hexstring_hash); return $hexstring_hash; } sub MoveUpdate { my %args = @_; my ($src, $dest); my $config = $args{'config'}; my $from = $args{'from'}; my $to = $args{'to'}; my $temp_prefix = lc($config->GetApp()); my $startdir = getcwd(); my $app = lc($config->GetApp()); MkdirWithPath("temp/$temp_prefix", 0, 0751) or die "Failed to mkpath temp/$temp_prefix"; chdir("temp/$temp_prefix"); my $tempdir = getcwd(); my $u_config = $config->{'mAppConfig'}->{'update_data'}; my @updates = keys %$u_config; #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'release'})); #printf("%s", Data::Dumper::Dumper(\@updates)); for my $u (@updates) { chdir("$u/aus2"); my $currdir = getcwd(); my $find_output = `find . -type d -name $from -maxdepth 6`; chomp($find_output); my @dirs = split(/\n/, $find_output); my @moves = map { $src = $dest = $_; $dest =~ s/^(.*)\/$from$/$1\/$to/g; { src => $src, dest => $dest } } @dirs; #printf("%s", Data::Dumper::Dumper(\@moves)); for my $m (@moves) { my $src = $m->{'src'}; my $dest = $m->{'dest'}; move($src, $dest) or warn "move($src, $dest) failed: $!"; } chdir($tempdir); } chdir($startdir); } # move_update sub run_download_complete_patches { my %args = @_; my $config = $args{'config'}; my $total = download_complete_patches(config => $config); download_complete_patches(total => $total, config => $config); } sub download_complete_patches { my %args = @_; my $config = $args{'config'}; my $i = 0; my $total = $args{'total'}; my $calculate_total = 0; if (defined($total)) { printf("Downloading complete patches - $total to download\n"); } else { $calculate_total = 1; } my $temp_prefix = lc($config->GetApp()); my $startdir = getcwd(); MkdirWithPath("temp/$temp_prefix", 0, 0751) or die "MkdirWithPath(temp/$temp_prefix) FAILED"; chdir("temp/$temp_prefix"); my $tempdir = getcwd(); my $fromReleaseVersion = $config->GetCurrentUpdate()->{'from'}; my $toReleaseVersion = $config->GetCurrentUpdate()->{'to'}; my $r_config = $config->{'mAppConfig'}->{'release'}; my @releases = ($fromReleaseVersion, $toReleaseVersion); for my $r (@releases) { my $rl_config = $r_config->{$r}; my $rlp_config = $rl_config->{'platforms'}; my @platforms = sort(keys(%{$rlp_config})); for my $p (@platforms) { my $platform_locales = $rlp_config->{$p}->{'locales'}; for my $l (@$platform_locales) { chdir($tempdir); MkdirWithPath("$r/ftp", 0, 0751) or die "Failed to mkpath $r/ftp"; chdir("$r/ftp"); my $download_url = $rl_config->{'completemarurl'}; $download_url = SubstitutePath(path => $download_url, platform => $p, locale => $l); my $output_filename = SubstitutePath( path => $MozAUSConfig::DEFAULT_MAR_NAME, platform => $p, locale => $l, version => $r, app => lc($config->GetApp())); next if -e $output_filename; $i++; next if $calculate_total; my $path = "."; if ( $output_filename =~ m/^(.*)\/([^\/]*)$/ ) { $path = $1; } MkdirWithPath($path, 0, 0751) or die "Failed to mkpath($path)"; chdir($path); my $download_url_s = $download_url; my $output_filename_s = $output_filename; #next if -e $output_filename; $download_url_s =~ s/^.*(.{57})$/...$1/ if (length($download_url_s) > 60); $output_filename_s =~ s/^.*(.{57})$/...$1/ if (length($output_filename_s) > 60); my $start_time = time(); PrintProgress(total => $total, current => $i, string => $output_filename_s); if (exists($rl_config->{'completemaruser'}) and exists($rl_config->{'completemarpasswd'})) { MozAUSLib::DownloadFile(url => $download_url, dest => $output_filename, user => $rl_config->{'completemaruser'}, password => $rl_config->{'completemarpasswd'} ); } else { MozAUSLib::DownloadFile(url => $download_url, dest => $output_filename ); } chdir("$tempdir/$r/ftp"); my $end_time = time(); my $total_time = $end_time - $start_time; if ( -f $output_filename ) { printf("done (" . $total_time . "s)\n"); } else { printf("failed (" . $total_time . "s)\n"); } select(undef, undef, undef, 0.5); } } } chdir($startdir); if (defined($total)) { printf("Finished downloading complete patches.\n"); } return $i; } # download_complete_patches sub PrintProgress { my %args = @_; my $currentStep = $args{'current'}; my $totalSteps = $args{'total'}; my $stepString = $args{'string'}; my $length = length($totalSteps); my $format = "[%${length}s/%${length}s]"; my $progressStr = sprintf($format, $currentStep, $totalSteps); print "\t$progressStr $stepString... "; } sub CreateCompletePatches { my %args = @_; my $config = $args{'config'}; my $update = $config->GetCurrentUpdate(); my $i = 0; my $total = 0; foreach my $plat (keys(%{$update->{'platforms'}})) { $total += scalar(keys(%{$update->{'platforms'}->{$plat}->{'locales'}})); } printf("Complete patches - $total to create\n"); my $temp_prefix = lc($config->GetApp()); my $startdir = getcwd(); MkdirWithPath("temp/$temp_prefix", 0, 0751) or die "Failed to mkpath(temp/$temp_prefix)"; chdir("temp/$temp_prefix"); my $u_config = $config->{'mAppConfig'}->{'update_data'}; my @updates = sort keys %$u_config; #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $complete = $u_config->{$u}->{'complete'}; my $complete_path = $complete->{'path'}; my $complete_url = $complete->{'url'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; my $to_name = $u_config->{$u}->{'to'}; my $gen_complete_path = $complete_path; $gen_complete_path = SubstitutePath(path => $complete_path, platform => $p, locale => $l); my $gen_complete_url = $complete_url; $gen_complete_url = SubstitutePath(path => $complete_url, platform => $p, locale => $l); #printf("%s", Data::Dumper::Dumper($to)); my $complete_pathname = "$u/ftp/$gen_complete_path"; # Go to next iteration if this partial patch already exists. next if -e $complete_pathname; if ( -f $to_path and ! -e $complete_pathname ) { my $start_time = time(); # copy complete to the expected result PrintProgress(total => $total, current => ++$i, string => "$u/$p/$l"); $complete_pathname =~ m/^(.*)\/[^\/]*/; my $parentdir = $1; MkdirWithPath($parentdir, 0, 0751) or die "Failed to mkpath($parentdir)"; system("rsync -a $to_path $complete_pathname"); my $end_time = time(); my $total_time = $end_time - $start_time; printf("done (" . $total_time . "s)\n"); } #last if $i > 2; #$i++; select(undef, undef, undef, 0.5); } #last; } #last; } #printf("%s", Data::Dumper::Dumper($u_config)); chdir($startdir); if (defined($total)) { printf("\n"); } return $i; } # create_complete_patches sub CreatePartialPatches { my %args = @_; my $config = $args{'config'}; my $update = $config->GetCurrentUpdate(); my $total = 0; my $i = 0; foreach my $plat (keys(%{$update->{'platforms'}})) { $total += scalar(keys(%{$update->{'platforms'}->{$plat}->{'locales'}})); } printf("Partial patches - $total to create\n"); my $temp_prefix = lc($config->GetApp()); my $startdir = getcwd(); MkdirWithPath("temp/$temp_prefix") or die "ASSERT: MkdirWithPath(temp/$temp_prefix) FAILED\n"; chdir("temp/$temp_prefix"); my $u_config = $config->{'mAppConfig'}->{'update_data'}; my @updates = sort keys %$u_config; #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $partial = $u_config->{$u}->{'partial'}; my $partial_path = $partial->{'path'}; my $partial_url = $partial->{'url'}; my $forcedUpdateList = $u_config->{$u}->{'force'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; my $to_name = $u_config->{$u}->{'to'}; my $gen_partial_path = $partial_path; $gen_partial_path = SubstitutePath(path => $partial_path, platform => $p, locale => $l ); my $gen_partial_url = $partial_url; $gen_partial_url = SubstitutePath(path => $partial_url, platform => $p, locale => $l ); #printf("%s", Data::Dumper::Dumper($to)); my $partial_pathname = "$u/ftp/$gen_partial_path"; # Go to next iteration if this partial patch already exists. next if -e $partial_pathname; $i++; if ( -f $from_path and -f $to_path and ! -e $partial_pathname ) { my $start_time = time(); PrintProgress(total => $total, current => $i, string => "$u/$p/$l"); my $rv = CreatePartialMarFile(from => $from_path, to => $to_path, mozdir => $config->GetToolsDir(), outputDir => getcwd(), outputFile => 'partial.mar', force => $forcedUpdateList); if ($rv <= 0) { die 'Partial mar creation failed (see error above?); ' . 'aborting.'; } # rename partial.mar to the expected result $partial_pathname =~ m/^(.*)\/[^\/]*$/g; my $partial_pathname_parent = $1; MkdirWithPath($partial_pathname_parent) or die "ASSERT: MkdirWithPath($partial_pathname_parent) FAILED\n"; move('partial.mar', $partial_pathname) or die "ASSERT: move(partial.mar, $partial_pathname) FAILED\n"; my $end_time = time(); my $total_time = $end_time - $start_time; printf("done (" . $total_time . "s)\n"); } #last if $i > 2; #$i++; select(undef, undef, undef, 0.5); } #last; } #last; } #printf("%s", Data::Dumper::Dumper($u_config)); chdir($startdir); if (defined($total)) { printf("\n"); } return $i; } # create_partial_patches sub get_aus_platform_string { my $short_platform = shift; my %aus_platform_strings = GetAUS2PlatformStrings(); my $aus_platform = $aus_platform_strings{$short_platform}; if (not defined($aus_platform)) { die "get_aus_platform_string(): Unknown short platform: $short_platform"; } return $aus_platform; } sub CreateCompletePatchinfo { my %args = @_; my $config = $args{'config'}; my $i = 0; my $total = 0; my $update = $config->GetCurrentUpdate(); my $temp_prefix = lc($config->GetApp()); my $startdir = getcwd(); MkdirWithPath("temp/$temp_prefix") or die "ASSERT: MkdirWithPath(temp/$temp_prefix) FAILED\n"; chdir("temp/$temp_prefix"); my $u_config = $config->GetAppConfig()->{'update_data'}; my @updates = sort keys %$u_config; foreach my $u (@updates) { my @channels = @{$u_config->{$u}->{'all_channels'}}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; foreach my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; foreach my $l (@locales) { foreach my $c (@channels) { $total++; } } } } printf("Complete patch info - $total to create\n"); #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $complete = $u_config->{$u}->{'complete'}; my $complete_path = $complete->{'path'}; my $complete_url = $complete->{'url'}; my @channels = @{$u_config->{$u}->{'all_channels'}}; my $channel = $u_config->{$u}->{'channel'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; my $to_name = $u_config->{$u}->{'to'}; # Build patch info my $from_aus_app = ucfirst($config->GetApp()); my $from_aus_version = $from->{'appv'}; my $from_aus_platform = get_aus_platform_string($p); my $from_aus_buildid = $from->{'build_id'}; my $gen_complete_path = $complete_path; $gen_complete_path = SubstitutePath(path => $complete_path, platform => $p, locale => $l ); my $complete_pathname = "$u/ftp/$gen_complete_path"; my $gen_complete_url = SubstitutePath(path => $complete_url, platform => $p, locale => $l ); my $detailsUrl = SubstitutePath( path => $u_config->{$u}->{'details'}, locale => $l, version => $to->{'appv'}); for my $c (@channels) { my $aus_prefix = "$u/aus2/$from_aus_app/$from_aus_version/$from_aus_platform/$from_aus_buildid/$l/$c"; my $complete_patch = $ul_config->{$l}->{'complete_patch'}; $complete_patch->{'info_path'} = "$aus_prefix/complete.txt"; # Go to next iteration if this partial patch already exists. next if ( -e $complete_patch->{'info_path'} or ! -e $complete_pathname ); $i++; #printf("partial = %s", Data::Dumper::Dumper($partial_patch)); #printf("complete = %s", Data::Dumper::Dumper($complete_patch)); PrintProgress(total => $total, current => $i, string => "$u/$p/$l/$c"); $complete_patch->{'patch_path'} = $to_path; $complete_patch->{'type'} = "complete"; my $hash_type = 'SHA1'; $complete_patch->{'hash_type'} = $hash_type; $complete_patch->{'hash_value'} = hash_file(file => $to_path); chomp($complete_patch->{'hash_value'}); $complete_patch->{'hash_value'} =~ s/^(\S+)\s+.*$/$1/g; $complete_patch->{'build_id'} = $to->{'build_id'}; $complete_patch->{'appv'} = $to->{'appv'}; $complete_patch->{'extv'} = $to->{'extv'}; $complete_patch->{'size'} = (stat($to_path))[7]; $complete_patch->{'url'} = $gen_complete_url; $complete_patch->{'details'} = $detailsUrl; write_patch_info(patch => $complete_patch, schemaVer => $to->{'schema'}); if (defined($u_config->{$u}->{'testchannel'})) { # Deep copy this data structure, since it's a copy of # $ul_config->{$l}->{'complete_patch'}; my $testPatch = {}; foreach my $key (keys(%{$complete_patch})) { $testPatch->{$key} = $complete_patch->{$key}; } if (exists($complete->{'testurl'})) { $testPatch->{'url'} = SubstitutePath(path => $complete->{'testurl'}, platform => $p, locale => $l ); } foreach my $testChan (split(/[\s,]+/, $u_config->{$u}->{'testchannel'})) { $testPatch->{'info_path'} = "$u/aus2.test/$from_aus_app/$from_aus_version/$from_aus_platform/$from_aus_buildid/$l/$testChan/complete.txt"; write_patch_info(patch => $testPatch, schemaVer => $to->{'schema'}); } } print("done\n"); } } } } chdir($startdir); printf("\n"); return $i; } # create_complete_patch_info sub CreatePastReleasePatchinfo { my %args = @_; my $config = $args{'config'}; my $patchInfoFilesCreated = 0; my $totalPastUpdates = 0; my $tempPrefix = lc($config->GetApp()); my $startDir = getcwd(); chdir("temp/$tempPrefix"); my $update = $config->GetCurrentUpdate(); my $prefixStr = "$update->{'from'}-$update->{'to'}"; foreach my $pastUpd (@{$config->GetPastUpdates()}) { my $fromRelease = $config->GetAppRelease($pastUpd->{'from'}); my @pastFromPlatforms = sort(keys(%{$fromRelease->{'platforms'}})); foreach my $fromPlatform (@pastFromPlatforms) { foreach my $locale (@{$fromRelease->{'platforms'}->{$fromPlatform}->{'locales'}}) { foreach my $channel (@{$pastUpd->{'channels'}}) { $totalPastUpdates++; } } } } # Multiply by two for the partial and the complete... $totalPastUpdates *= 2; printf("Past release patch info - $totalPastUpdates to create\n"); foreach my $pastUpd (@{$config->GetPastUpdates()}) { my $fromRelease = $config->GetAppRelease($pastUpd->{'from'}); my $currentReleaseVersion = $config->GetCurrentUpdate()->{'to'}; my @pastFromPlatforms = sort(keys(%{$fromRelease->{'platforms'}})); my $complete = $config->GetCurrentUpdate()->{'complete'}; my $completePath = $complete->{'path'}; my $completeUrl = $complete->{'url'}; my $completeTestUrl = $complete->{'testurl'}; foreach my $fromPlatform (@pastFromPlatforms) { # XXX - This is a hack, solely to support the fact that "mac" # now means "universal binaries," but for 1.5.0.2 and 1.5.0.3, there # was "mac" which meant PPC and "unimac," which meant universal # binaries. Unfortunately, we can't just make > 1.5.0.4 use a # platform of "unimac," because all the filenames are foo.mac.dmg, # not foo.unimac.dmg. Le sigh. # # So, what this does is checks if $patchPlatformNode is null AND # our platform is macppc; we want all the old macppc builds to # update to the universal builds; so, we can get the proper locales # and build IDs by grabbing the proper patchPlatformNode using # the a key of 'mac' to generate the right strings. # # But, that's not all; we need to support this concept of "platform # transformations," so now we have a "fromPlatform" and a # "toPlatform" which, MOST of the time, will be the same, but # for this specific case, won't be. my $toPlatform = $fromPlatform; my $patchPlatformNode = $update->{'platforms'}->{$toPlatform}; if ($patchPlatformNode eq undef && $fromPlatform eq 'macppc') { $toPlatform = 'mac'; $patchPlatformNode = $update->{'platforms'}->{$toPlatform}; } foreach my $locale (@{$fromRelease->{'platforms'}->{$fromPlatform}->{'locales'}}) { my $patchLocaleNode = $patchPlatformNode->{'locales'}->{$locale}->{'to'}; if ($patchLocaleNode eq undef) { print STDERR "No known patch for locale $locale, $fromRelease->{'version'} -> $currentReleaseVersion; skipping...\n"; next; } my $to_path = $patchLocaleNode->{'path'}; # Build patch info my $fromAusApp = ucfirst($config->GetApp()); my $fromAusVersion = $pastUpd->{'from'}; my $fromAusPlatform = get_aus_platform_string($fromPlatform); my $fromAusBuildId = $fromRelease->{'platforms'}->{$fromPlatform}->{'build_id'}; my $genCompletePath = SubstitutePath(path => $completePath, platform => $toPlatform, locale => $locale ); my $completePathname = "$prefixStr/ftp/$genCompletePath"; my $genCompleteUrl = SubstitutePath(path => $completeUrl, platform => $toPlatform, locale => $locale ); my $genCompleteTestUrl = SubstitutePath(path => $completeTestUrl, platform => $toPlatform, locale => $locale ); my $detailsUrl = SubstitutePath( path => $config->GetCurrentUpdate()->{'details'}, locale => $locale, version => $patchLocaleNode->{'appv'}); foreach my $channel (@{$pastUpd->{'channels'}}) { my $ausDir = ($channel =~ /test$/) ? 'aus2.test' : 'aus2'; my $ausPrefix = "$prefixStr/$ausDir/$fromAusApp/$fromAusVersion/$fromAusPlatform/$fromAusBuildId/$locale/$channel"; my $completePatch = {}; $completePatch ->{'info_path'} = "$ausPrefix/complete.txt"; my $prettyPrefix = "$pastUpd->{'from'}-$update->{'to'}"; PrintProgress(total => $totalPastUpdates, current => ++$patchInfoFilesCreated, string => "$prettyPrefix/$fromAusPlatform/$locale/$channel/complete"); # Go to next iteration if this partial patch already exists. #next if ( -e $complete_patch->{'info_path'} or ! -e $complete_pathname ); $completePatch->{'patch_path'} = $to_path; $completePatch->{'type'} = 'complete'; my $hash_type = 'SHA1'; $completePatch->{'hash_type'} = 'SHA1'; $completePatch->{'hash_value'} = hash_file(file => $to_path); $completePatch->{'build_id'} = $patchLocaleNode->{'build_id'}; $completePatch->{'appv'} = $patchLocaleNode->{'appv'}; $completePatch->{'extv'} = $patchLocaleNode->{'extv'}; $completePatch->{'size'} = (stat($to_path))[$MozAUSLib::ST_SIZE]; $completePatch->{'url'} = ($channel =~ /test$/) ? $genCompleteTestUrl : $genCompleteUrl; $completePatch->{'details'} = $detailsUrl; write_patch_info(patch => $completePatch, schemaVer => $patchLocaleNode->{'schema'}); print("done\n"); # Now, write the same information as a partial, since # for now, we publish the "partial" and "complete" updates # as pointers to the complete. $completePatch->{'type'} = 'partial'; $completePatch->{'info_path'} = "$ausPrefix/partial.txt"; PrintProgress(total => $totalPastUpdates, current => ++$patchInfoFilesCreated, string => "$prettyPrefix/$fromAusPlatform/$locale/$channel/partial"); write_patch_info(patch => $completePatch, schemaVer => $patchLocaleNode->{'schema'}); print("done\n"); } } } } chdir($startDir); printf("\n"); } sub CreatePartialPatchinfo { my %args = @_; my $config = $args{'config'}; my $i = 0; my $total = 0; #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); my $temp_prefix = lc($config->GetApp()); my $startdir = getcwd(); MkdirWithPath("temp/$temp_prefix") or die "ASSERT: MkdirWithPath(temp/$temp_prefix) FAILED\n"; chdir("temp/$temp_prefix"); my $u_config = $config->{'mAppConfig'}->{'update_data'}; my @updates = sort keys %$u_config; # TODO - This could be cleaner. foreach my $u (@updates) { my @channels = @{$u_config->{$u}->{'all_channels'}}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; foreach my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; foreach my $l (@locales) { foreach my $c (@channels) { $total++; } } } } printf("Partial patch info - $total to create\n"); #printf("%s", Data::Dumper::Dumper($config->{'app_config'}->{'update_data'})); for my $u (@updates) { my $partial = $u_config->{$u}->{'partial'}; my $partial_path = $partial->{'path'}; my $partial_url = $partial->{'url'}; my @channels = @{$u_config->{$u}->{'all_channels'}}; my $channel = $u_config->{$u}->{'channel'}; my @platforms = sort keys %{$u_config->{$u}->{'platforms'}}; for my $p (@platforms) { my $ul_config = $u_config->{$u}->{'platforms'}->{$p}->{'locales'}; my @locales = sort keys %$ul_config; for my $l (@locales) { my $from = $ul_config->{$l}->{'from'}; my $to = $ul_config->{$l}->{'to'}; my $from_path = $from->{'path'}; my $to_path = $to->{'path'}; #my $to_name = $u_config->{$u}->{'to'}; # Build patch info my $from_aus_app = ucfirst($config->GetApp()); my $from_aus_version = $from->{'appv'}; my $from_aus_platform = get_aus_platform_string($p); my $from_aus_buildid = $from->{'build_id'}; my $gen_partial_path = $partial_path; $gen_partial_path = SubstitutePath(path => $partial_path, platform => $p, locale => $l ); my $partial_pathname = "$u/ftp/$gen_partial_path"; my $gen_partial_url = $partial_url; $gen_partial_url = SubstitutePath(path => $partial_url, platform => $p, locale => $l ); my $detailsUrl = SubstitutePath( path => $u_config->{$u}->{'details'}, locale => $l, version => $to->{'appv'}); for my $c (@channels) { my $aus_prefix = "$u/aus2/$from_aus_app/$from_aus_version/$from_aus_platform/$from_aus_buildid/$l/$c"; my $partial_patch = $ul_config->{$l}->{'partial_patch'}; $partial_patch->{'info_path'} = "$aus_prefix/partial.txt"; # Go to next iteration if this partial patch already exists. next if ( -e $partial_patch->{'info_path'} or ! -e $partial_pathname ); $i++; PrintProgress(total => $total, current => $i, string => "$u/$p/$l/$c"); $partial_patch->{'patch_path'} = $partial_pathname; $partial_patch->{'type'} = "partial"; my $hash_type = 'SHA1'; $partial_patch->{'hash_type'} = $hash_type; $partial_patch->{'hash_value'} = hash_file(file => $partial_pathname); chomp($partial_patch->{'hash_value'}); $partial_patch->{'hash_value'} =~ s/^(\S+)\s+.*$/$1/g; $partial_patch->{'build_id'} = $to->{'build_id'}; $partial_patch->{'appv'} = $to->{'appv'}; $partial_patch->{'extv'} = $to->{'extv'}; $partial_patch->{'size'} = (stat($partial_pathname))[7]; $partial_patch->{'url'} = $gen_partial_url; $partial_patch->{'details'} = $detailsUrl; write_patch_info(patch => $partial_patch, schemaVer => $to->{'schema'}); if (defined($u_config->{$u}->{'testchannel'})) { # Deep copy this data structure, since it's a copy of # $ul_config->{$l}->{'complete_patch'}; my $testPatch = {}; foreach my $key (keys(%{$partial_patch})) { $testPatch->{$key} = $partial_patch->{$key}; } if (exists($partial->{'testurl'})) { $testPatch->{'url'} = SubstitutePath(path => $partial->{'testurl'}, platform => $p, locale => $l ); } foreach my $testChan (split(/[\s,]+/, $u_config->{$u}->{'testchannel'})) { $testPatch->{'info_path'} = "$u/aus2.test/$from_aus_app/$from_aus_version/$from_aus_platform/$from_aus_buildid/$l/$testChan/partial.txt"; #print STDERR "Generating TEST entry: $testPatch->{'info_path'}\n"; write_patch_info(patch => $testPatch, schemaVer => $to->{'schema'}); } } printf("done\n"); } } } } chdir($startdir); if (defined($total)) { printf("\n"); } return $i; } # create_partial_patch_info sub write_patch_info { my %args = @_; my $patch = $args{'patch'}; my $schemaVersion = $args{'schemaVer'} || $DEFAULT_SCHEMA_VERSION; my $info_path = $patch->{'info_path'}; $info_path =~ m/^(.*)\/[^\/]*$/; my $info_path_parent = $1; my $text; if ($DEFAULT_SCHEMA_VERSION == $schemaVersion) { $text = "$patch->{'type'}\n"; $text .= "$patch->{'url'}\n"; $text .= "$patch->{'hash_type'}\n"; $text .= "$patch->{'hash_value'}\n"; $text .= "$patch->{'size'}\n"; $text .= "$patch->{'build_id'}\n"; $text .= "$patch->{'appv'}\n"; $text .= "$patch->{'extv'}\n"; if (defined($patch->{'details'})) { $text .= "$patch->{'details'}\n"; } } elsif ($CURRENT_SCHEMA_VERSION == $schemaVersion) { $text = "version=1\n"; $text .= "type=$patch->{'type'}\n"; $text .= "url=$patch->{'url'}\n"; $text .= "hashFunction=$patch->{'hash_type'}\n"; $text .= "hashValue=$patch->{'hash_value'}\n"; $text .= "size=$patch->{'size'}\n"; $text .= "build=$patch->{'build_id'}\n"; $text .= "appv=$patch->{'appv'}\n"; $text .= "extv=$patch->{'extv'}\n"; if (defined($patch->{'details'})) { $text .= "detailsUrl=$patch->{'details'}\n"; } if (defined($patch->{'license'})) { $text .= "licenseUrl=$patch->{'license'}\n"; } if (defined($patch->{'updateType'})) { $text .= "updateType=$patch->{'updateType'}\n"; } } else { die "ASSERT: Invalid schema version: $schemaVersion\n"; } MkdirWithPath($info_path_parent) or die "MkdirWithPath($info_path_parent) FAILED"; open(PATCHINFO, ">$patch->{'info_path'}") or die "ERROR: Couldn't open $patch->{'info_path'} for writing!"; print PATCHINFO $text; close(PATCHINFO); } # write_patch_info sub Startup { # A bunch of assumptions are made that this is NOT Win32; assert that... die "ASSERT: Can not currently run on Win32.\n" if ($OSNAME eq 'MSWin32'); expire_or_win(); } sub Shutdown { print STDERR "IN SHUTDOWN...\n"; my $rv = unlink($PID_FILE); # We should probably die in this condition, but... since we're shutting # down, who cares? print STDERR "Failed to remove $PID_FILE: $ERRNO\n" if (not $rv); } # Contest subroutine that will either die or, if it returns, we have authority # over other instances of this script to proceed. sub expire_or_win { # Create the pid file before continuing. system("touch $PID_FILE"); # Open the pid file for update (modes read and write). open(FH, "+<$PID_FILE") or die "Cannot open $PID_FILE for update: $!\n"; # Obtain a file lock on the handle. flock(FH, 2); # Gather existing data from the file. my $existing_data; { local($/) = undef; $existing_data = ; } chomp($existing_data); # If the existing data is a process ID that is already alive, die. if ( length($existing_data) > 0 and process_is_alive($existing_data) ) { die("System already running with PID $existing_data!\n"); } # Otherwise, we reset the file handle and truncate the pid file, then write # our PID into it. seek(FH, 0, 0); truncate(FH, 0); print FH "$PID\n"; # All done with the pid file. close(FH); } sub process_is_alive { my ($pid) = @_; my $psout = `ps -A | grep $pid | grep -v grep | awk "{if (\\\$1 == $pid) print}"`; length($psout) > 0 ? 1 : 0; } ## ## ENTRY POINT (Yes, aaaalll the way down here...) ## $SIG{'__DIE__'} = \&Shutdown; $SIG{'INT'} = \&Shutdown; main();