diff --git a/webtools/tinderbox/INSTALL b/webtools/tinderbox/INSTALL index aa8ee23ad5d..3e6fba2c37a 100644 --- a/webtools/tinderbox/INSTALL +++ b/webtools/tinderbox/INSTALL @@ -75,21 +75,35 @@ This is a quick guide for installing the tinderbox server. all mail through the handemail.pl script. Assuming that you're using sendmail, add the following line to /etc/aliases -tinderbox-daemon: "|/var/www/html/tinderbox/handlemail.pl /var/www/html/tinderbox" +tinderbox-daemon: "|/var/www/html/tinderbox/handlemail.pl" If sendmail uses smrsh, you may need to create a symlink under /etc/smrsh. ln -sf /var/www/html/tinderbox/handlemail.pl /etc/smrsh/handlemail.pl 8) + As the tinderbox user, add a cron job to call 'processbuild.pl'. + For example, + MAILTO="root" + USER=tinderbox + */5 * * * * /var/www/html/tinderbox/processbuild.pl + This will cause the tinderbox mail to be processed every five minutes and + to mail the root user if any errors occur. + +9) Generate an admin password to be used to setup new trees sudo make -C mozilla/webtools/tinderbox genpasswd -9) +10) Restart apache and go to http://tinderbox.company.com/tinderbox/ . Click on 'Create new tree' and enter the information for your new tinderbox tree. -10) +11) Pull the tinderbox client source and start sending build reports to tinderbox-daemon@ (cd /builds/cvs && cvs -z3 co mozilla/tools/tinderbox ) + +12) + If you are migrating from an older tinderbox installation, you may need + to run ./checksetup.pl to make sure that your trees are using the + correct treedata.pl format. diff --git a/webtools/tinderbox/buildwho.pl b/webtools/tinderbox/buildwho.pl index 986cec1b4b6..67fa35135cd 100755 --- a/webtools/tinderbox/buildwho.pl +++ b/webtools/tinderbox/buildwho.pl @@ -20,13 +20,11 @@ # # Contributor(s): -use FileHandle; use File::Copy 'move'; -use Fcntl qw(:DEFAULT :flock); use lib "@TINDERBOX_DIR@"; require 'tbglobals.pl'; -$F_DEBUG=1; +$F_DEBUG=0; $ENV{'PATH'} = "@SETUID_PATH@"; @@ -59,7 +57,8 @@ exit 0 if (!$use_bonsai && !$use_viewvc); # Only allow one process at a time to re-write "who.dat". # -my $lock = lock_datafile($tree); +my $lockfile = "$tree/buildwho.sem"; +my $lock = lock_datafile($lockfile); if ($use_bonsai) { # Setup global variables for bonsai query @@ -88,7 +87,7 @@ if ($use_bonsai) { build_who($tree); unlock_datafile($lock); - +unlink($lockfile); # End of main ################################################################## sub usage() { @@ -96,26 +95,6 @@ sub usage() { exit 1; } -sub lock_datafile { - my ($tree) = @_; - - my $lock_fh = new FileHandle ">>$tree/buildwho.sem" - or die "Couldn't open semaphore file!"; - - # Get an exclusive lock with a non-blocking request - unless (flock($lock_fh, LOCK_EX|LOCK_NB)) { - die "buildwho.pl: Lock unavailable: $!"; - } - return $lock_fh; -} - -sub unlock_datafile { - my ($lock_fh) = @_; - - flock $lock_fh, LOCK_UN; # Free the lock - close $lock_fh; -} - sub build_who { my ($tree) = @_; $query_date_min = time - (60 * 60 * 24 * $days); diff --git a/webtools/tinderbox/handlemail.pl b/webtools/tinderbox/handlemail.pl index 1d2d4ce61e6..6969ab470f7 100755 --- a/webtools/tinderbox/handlemail.pl +++ b/webtools/tinderbox/handlemail.pl @@ -20,13 +20,19 @@ # # Contributor(s): -$ENV{'PATH'} = "@SETUID_PATH@"; +use lib "@TINDERBOX_DIR@"; +require 'tbglobals.pl'; +$ENV{'PATH'} = "@SETUID_PATH@"; $tinderboxdir = "@TINDERBOX_DIR@"; -$err = system("cat | $tinderboxdir/processbuild.pl"); +chdir $tinderboxdir or die "Couldn't chdir to $tinderboxdir"; -if( $err ) { - die "processbuild.pl returned an error\n"; +$time = time(); +open(OUT, ">$data_dir/tbx.$time.$$") or die ("Could not open data file, tbx.$time.$$\n"); +while () { + print OUT $_; } +close(OUT); +exit(0); diff --git a/webtools/tinderbox/processbuild.pl b/webtools/tinderbox/processbuild.pl index 530eb8a9b39..4365b5ce25b 100755 --- a/webtools/tinderbox/processbuild.pl +++ b/webtools/tinderbox/processbuild.pl @@ -22,112 +22,161 @@ use Compress::Zlib; use Compress::Bzip2; +use Getopt::Long; use Time::Local; use lib "@TINDERBOX_DIR@"; require 'tbglobals.pl'; # for $gzip +#use strict; umask 002; +# setuid globals $ENV{'PATH'} = "@SETUID_PATH@"; - $tinderboxdir = "@TINDERBOX_DIR@"; -chdir $tinderboxdir || die "Couldn't chdir to $tinderboxdir"; +# globals +my ($only_check_mail); +my @changed_trees=(); +my %scraped_trees; +my $debug = 0; -if ($ARGV[0] eq '--check-mail') { - $only_check_mail = 1; - shift @ARGV; +chdir $tinderboxdir or die "Couldn't chdir to $tinderboxdir"; + +# parse args +GetOptions("check-mail" => \$only_check_mail) or die ("Error parsing args."); + +# Acquire a lock first so that we don't step on ourselves +my $lockfile = "$data_dir/processbuild.sem"; +my $lock = &lock_datafile($lockfile); +opendir(DIR, $data_dir) or die("Can't opendir($data_dir): $!"); +my @datafiles = + sort(grep { /^tbx\.\d+\.\d+$/ && -f "$data_dir/$_" } readdir(DIR)); +closedir(DIR); +print "Files: @datafiles\n" if ($debug && $#datafiles > 0); +for my $file (@datafiles) { + &process_mailfile("$data_dir/$file"); } +&unlock_datafile($lock); +unlink($lockfile); -# If datafile is given on the commandline, use it. Otherwise, read from STDIN -$mail_file = shift; -if (!defined($mail_file)) { - $mail_file = "data/tbx.$$"; - - open(DF, ">", $mail_file) || die "could not open $mail_file"; - while(){ - print DF $_; - } - close(DF); -} - -%MAIL_HEADER = (); -%tinderbox = (); - -# Scan the logfile once to get mail header and build variables -# -open(LOG, "<", $mail_file) or die "Can't open $!"; - -parse_mail_header(*LOG, \%MAIL_HEADER); -parse_log_variables(*LOG, \%tinderbox); - -close LOG; - -# If the mail does not contain any tinderbox header info, just drop it. -@tbkeys = keys %tinderbox; -if ($#tbkeys == -1) { - unlink $mail_file; - exit 0; -} - -# Make sure variables are defined correctly -# -check_required_variables(\%tinderbox, \%MAIL_HEADER); - -die "Mail variables passed the test\n" if $only_check_mail; - -# Write data to "build.dat" -# -$tinderbox{logfile} = "$tinderbox{builddate}.$$.gz"; -write_build_data(\%tinderbox); - -# Compress the build log and put it in the tree -# -compress_log_file(\%tinderbox, $mail_file) - unless $tinderbox{status} =~ /building/; - -unlink $mail_file; - - -# Who data -# -$err = system("./buildwho.pl", "$tinderbox{tree}"); -if ($err) { - die "buildwho.pl returned an error\n"; -} - - -# Warnings -# Compare the name with $warning_buildnames_pat which is defined in -# $tinderbox{tree}/treedata.pl if at all. -require "$tinderbox{tree}/treedata.pl" if -r "$tinderbox{tree}/treedata.pl"; -if (defined $warning_buildnames_pat - and $tinderbox{build} =~ /^$warning_buildnames_pat$/ - and $tinderbox{status} ne 'failed') { - $err = system("./warnings.pl", "$tinderbox{tree}/$tinderbox{logfile}"); - die "warnings.pl returned an error\n" if ($err); -} - -# Scrape data -# Look for build name in scrapedata.pl. -require "$tinderbox{tree}/scrapebuilds.pl" if -r "$tinderbox{tree}/scrapebuilds.pl"; -if ($scrape_builds->{$tinderbox{build}} - and $tinderbox{status} ne 'building') { - $err = system("./scrape.pl", "$tinderbox{tree}", "$tinderbox{logfile}"); - die "scrape.pl returned an error\n" if ($err); -} - -# Static pages -# For Sidebar flash and tinderbox panels. -my $rel_path = ''; require 'showbuilds.pl'; -$tree = $tinderbox{tree}; -&tb_build_static(); - +for my $t (@changed_trees) { + $tree = $t; + print "Tree: $t\n" if ($debug); + # Static pages - For Sidebar flash and tinderbox panels. + $rel_path = ''; + &tb_build_static(); + # Who data + $err = system("./buildwho.pl", "$t"); + if ($err) { + warn "buildwho.pl returned an error on file $mail_file\n"; + } +} +exit(0); # end of main ###################################################################### +sub process_mailfile($) { + my ($mail_file) = @_; + my $err = 0; + + print "process_mailfile($mail_file)\n" if ($debug); + + %MAIL_HEADER = (); + %tinderbox = (); + + # Scan the logfile once to get mail header and build variables + # + print "Parsing: begin\n" if ($debug); + unless (open(LOG, "<", $mail_file)) { + warn "Can't open $mail_file: $!"; + return; + } + parse_mail_header(*LOG, \%MAIL_HEADER); + parse_log_variables(*LOG, \%tinderbox); + close LOG; + + print "Parsing: end\n" if ($debug); + + # If the mail does not contain any tinderbox header info, just drop it. + @tbkeys = keys %tinderbox; + if ($#tbkeys == -1) { + print "Dropping spam mail: $mail_file\n" if ($debug); + unlink $mail_file; + return; + } + + # Make sure variables are defined correctly + # + return if (&check_required_variables(\%tinderbox, \%MAIL_HEADER)); + + if ($only_check_mail) { + warn "Mail variables passed the test\n"; + return; + } + + # Write data to "build.dat" + # + print "Write build.dat: $tinderbox{build}\n" if ($debug); + $tinderbox{logfile} = "$tinderbox{builddate}.$$.gz"; + write_build_data(\%tinderbox); + + # Add tree to changed trees list to later rebuild who.dat + # + if (!grep(/$tinderbox{tree}/, @changed_trees)) { + push @changed_trees, $tinderbox{tree}; + } + + # Compress the build log and put it in the tree + # + print "Compress\n" if ($debug); + if ($tinderbox{status} =~ /building/) { + unlink $mail_file; + print "process_mailfile($mail_file) Building: END\n" if ($debug); + return; + } else { + return if (&compress_log_file(\%tinderbox, $mail_file)); + } + + # Warnings + # Compare the name with $warning_buildnames_pat which is defined in + # $tinderbox{tree}/treedata.pl if at all. + print "Warnings($tinderbox{tree}/$tinderbox{logfile})\n" if ($debug); + undef $warning_buildnames_pat; + open(TD, "$tinderbox{tree}/treedata.pl"); + while ($line=) { + if ($line =~ m/^\$warning_build_names_pat\s*=.*;$/) { + eval($line); + } + } + close(TD); + if (defined $warning_buildnames_pat + and $tinderbox{build} =~ /^$warning_buildnames_pat$/ + and $tinderbox{status} ne 'failed') { + $err = system("./warnings.pl", "$tinderbox{tree}/$tinderbox{logfile}"); + warn "warnings.pl($tinderbox{tree}/$tinderbox{logfile} returned an error\n" if ($err); + undef $warning_buildnames_pat; + } + + # Scrape data + # Look for build name in scrapedata.pl. + print "Scrape($tinderbox{tree},$tinderbox{logfile})\n" if ($debug); + undef $scrape_builds; + require "$tinderbox{tree}/scrapebuilds.pl" + if -r "$tinderbox{tree}/scrapebuilds.pl"; + # required files are only loaded once so preserve scraped_builds value + if (defined($scrape_builds)) { + $scraped_trees{$tinderbox{tree}} = $scrape_builds; + } + $sb = $scraped_trees{$tinderbox{tree}}; + if (defined($sb) and $sb->{$tinderbox{build}}) { + $err = system("./scrape.pl", "$tinderbox{tree}", "$tinderbox{logfile}"); + warn "scrape.pl($tinderbox{tree},$tinderbox{logfile}) returned an error\n" if ($err); + } + print "process_mailfile($mail_file) END\n" if ($debug); +} + # This routine will scan through log looking for 'tinderbox:' variables # sub parse_log_variables { @@ -226,23 +275,40 @@ sub check_required_variables { # Report errors # - die $err_string unless $err_string eq ''; + if ($err_string eq '') { + return 0; + } else { + warn $err_string; + return 1; + } } sub write_build_data { my $tbx = $_[0]; $process_time = time; - open(BUILDDATA, ">>", "$tbx->{tree}/build.dat") - or die "can't open $! for writing"; + my $lockfile = "$tbx->{tree}/builddat.sem"; + my $lock = &lock_datafile($lockfile); + unless (open(BUILDDATA, ">>", "$tbx->{tree}/build.dat")) { + warn "can't open $tbx->{tree}/build.dat for writing: $!"; + &unlock_datafile($lock); + return; + } print BUILDDATA "$process_time|$tbx->{builddate}|$tbx->{build}|$tbx->{errorparser}|$tbx->{status}|$tbx->{logfile}|$tbx->{binaryurl}\n"; close BUILDDATA; + &unlock_datafile($lock); + unlink($lockfile); } sub compress_log_file { my ($tbx, $maillog) = @_; local *LOG2; + my $err = 0; - open(LOG2, "<", $maillog) or die "cant open $!"; + open(LOG2, "<", $maillog) or $err++; + if ($err) { + warn "Can't open $maillog: $!"; + return 1; + } # Skip past the the RFC822.HEADER # @@ -251,8 +317,12 @@ sub compress_log_file { last if /^$/; } - my $gz = gzopen("$tbx->{tree}/$tbx->{logfile}","wb") or - die "gzopen($tbx->{tree}/$tbx->{logfile}): $!\n"; + my $logfile = "$tbx->{tree}/$tbx->{logfile}"; + my $gz = gzopen($logfile,"wb") or $err++; + if ($err) { + warn "gzopen($logfile): $!\n"; + return 1; + } # If this log is compressed, we need to decode it and decompress # it before storing its contents into ZIPLOG. @@ -270,16 +340,27 @@ sub compress_log_file { my $decoded = "$tbx->{tree}/$tbx->{logfile}.uncomp"; if ($tbx->{logencoding} eq 'base64') { eval "use MIME::Base64 ();"; - open(DECODED, ">", $decoded) - or die "Can't open $decoded for writing: $!"; + open(DECODED, ">", $decoded) or $err++; + if ($err) { + warn "Can't open $decoded for writing: $!"; + close(LOG2); + $gz->gzclose(); + unlink $logfile; + return 1; + } while () { print DECODED MIME::Base64::decode($_); } close DECODED; } elsif ($tbx->{logencoding} eq 'uuencode') { - open(DECODED, ">", $decoded) - or die "Can't open $decoded for writing: $!"; + open(DECODED, ">", $decoded) or $err++; + if ($err) { + close(LOG2); + $gz->gzclose(); + unlink $logfile; + return 1; + } while () { print DECODED unpack("u*", $_); } @@ -289,16 +370,30 @@ sub compress_log_file { # Decompress the log using the logcompression variable to determine # the type of compression used. if ($tbx->{logcompression} eq 'gzip') { - my $comp_gz = gzopen($decoded, "rb") or - die ("$decoded: $!\n"); + my $comp_gz = gzopen($decoded, "rb") or $err++; + if ($err) { + warn ("$decoded: $!\n"); + close(LOG2); + $gz->gzclose(); + unlink $logfile; + unlink $decoded; + return 1; + } my ($bytesread, $line); while (($bytesread = $comp_gz->gzread($line)) > 0) { $gz->gzwrite($line); } $comp_gz->gzclose(); } elsif ($tbx->{logcompression} eq 'bzip2') { - my $comp_bz = bzopen($decoded, "rb") or - die ("$decoded: $!\n"); + my $comp_bz = bzopen($decoded, "rb") or $err++; + if ($err) { + warn ("$decoded: $!\n"); + close(LOG2); + $gz->gzclose(); + unlink $logfile; + unlink $decoded; + return 1; + } my ($bytesread, $line); while (($bytesread = $comp_bz->bzread($line)) > 0) { $gz->gzwrite($line); @@ -317,4 +412,6 @@ sub compress_log_file { } $gz->gzclose(); close LOG2; + unlink $maillog; + return 0; } diff --git a/webtools/tinderbox/scrape.pl b/webtools/tinderbox/scrape.pl index 979cc4462f0..10117ec897f 100755 --- a/webtools/tinderbox/scrape.pl +++ b/webtools/tinderbox/scrape.pl @@ -33,6 +33,7 @@ sub usage { use Compress::Zlib; use lib "@TINDERBOX_DIR@"; require "tbglobals.pl"; +my $debug = 0; $ENV{PATH} = "@SETUID_PATH@"; @@ -43,6 +44,8 @@ unless ($#ARGV == 1) { ($tree, $logfile) = @ARGV; +print "scrape.pl($tree, $logfile)\n" if ($debug); + $tree = &trick_taint($tree); $logfile = &trick_taint($logfile); @@ -63,9 +66,13 @@ if (!defined(@scrape_data)) { # Save the scrape data to 'scrape.dat' # +my $lockfile = "$tree/scrape.sem"; +my $lock = &lock_datafile($lockfile); open(SCRAPE, ">>", "$tree/scrape.dat") or die "Unable to open $tree/scrape.dat"; print SCRAPE "$logfile|".join('|', @scrape_data)."\n"; close SCRAPE; +&unlock_datafile($lock); +unlink($lockfile); #print "scrape_data = "; #my $i; @@ -96,8 +103,7 @@ sub find_scrape_data { # No longer use ; to create separate lines. #@line = split(';', $_); - $line[0] = $gzline; - push(@rv, @line); + push(@rv, $gzline); } } return @rv; diff --git a/webtools/tinderbox/tbglobals.pl b/webtools/tinderbox/tbglobals.pl index f2c6fbaab6a..c7506a4aa89 100755 --- a/webtools/tinderbox/tbglobals.pl +++ b/webtools/tinderbox/tbglobals.pl @@ -23,6 +23,8 @@ use Backwards; use Digest::MD5 qw(md5_hex); use Tie::IxHash; +use FileHandle; +use Fcntl qw(:DEFAULT :flock); # # Global variabls and functions for tinderbox @@ -111,7 +113,7 @@ sub trick_taint{ sub make_tree_list { my @result; while(<*>) { - if( -d $_ && $_ ne 'data' && $_ ne 'CVS' && -f "$_/treedata.pl") { + if( -d $_ && $_ ne "$data_dir" && $_ ne 'CVS' && -f "$_/treedata.pl") { push @result, $_; } } @@ -126,10 +128,24 @@ sub require_only_one_tree { &show_tree_selector, exit if $t eq ''; } -sub lock{ +sub lock_datafile { + my ($file) = @_; + + my $lock_fh = new FileHandle ">>$file" + or die "Couldn't open semaphore file, $file: $!"; + + # Get an exclusive lock with a non-blocking request + unless (flock($lock_fh, LOCK_EX|LOCK_NB)) { + die "Lock unavailable: $!"; + } + return $lock_fh; } -sub unlock{ +sub unlock_datafile { + my ($lock_fh) = @_; + + flock $lock_fh, LOCK_UN; # Free the lock + close $lock_fh; } sub print_time { diff --git a/webtools/tinderbox/warnings.pl b/webtools/tinderbox/warnings.pl index 106c32253e8..c5337938f08 100755 --- a/webtools/tinderbox/warnings.pl +++ b/webtools/tinderbox/warnings.pl @@ -203,10 +203,14 @@ my $total_unignored_warnings = $total_warnings_count - $total_ignored_count; if ($total_unignored_warnings > 0) { # Add an entry to the warning log # + my $lockfile = "$tree/warnings.sem"; + my $lock = &lock_datafile($lockfile); my $warn_log = "$tree/warnings.dat"; $fh->open($warn_log, ">>") or die "Unable to open $warn_log: $!\n"; print $fh "$log_file|$total_unignored_warnings\n"; $fh->close; + &unlock_datafile($lock); + unlink($lockfile); } # end of main