# buildd-uploader: upload finished packages for buildd # Copyright © 1998 Roman Hodek # Copyright © 2009 Roger Leigh # Copyright © 2005 Ryan Murray # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see # . # ####################################################################### package Buildd::Uploader; use strict; use warnings; use Buildd qw(lock_file unlock_file unset_env exitstatus send_mail); use Buildd::Base; use Buildd::Conf qw(); BEGIN { use Exporter (); our (@ISA, @EXPORT); @ISA = qw(Exporter Buildd::Base); @EXPORT = qw(); } sub new { my $class = shift; my $conf = shift; my $self = $class->SUPER::new($conf); bless($self, $class); $self->set('Uploader Lock', undef); $self->set('Uploaded Pkgs', {}); $self->open_log(); return $self; } sub run { my $self = shift; unset_env(); $self->set('Uploader Lock', lock_file("$main::HOME/buildd-uploader", 1)); if (!$self->get('Uploader Lock')) { $self->log("exiting; another buildd-uploader is still running"); return 1; } for my $queue_config (@{$self->get_conf('UPLOAD_QUEUES')}) { $self->upload( $queue_config->get('DUPLOAD_LOCAL_QUEUE_DIR'), $queue_config->get('DUPLOAD_ARCHIVE_NAME')); } my $uploaded_pkgs = $self->get('Uploaded Pkgs'); foreach my $archdist (keys %{$uploaded_pkgs}) { $self->log("Set to Uploaded($archdist):$uploaded_pkgs->{$archdist}"); } return 0; } sub uploaded ($@) { my $self = shift; my $pkg = shift; my $arch_name = shift; my $dist_name = shift; my $msgs = ""; my $dist_config = $self->get_arch_dist_config_by_name($arch_name, $dist_name); my $db = $self->get_db_handle($dist_config); my $pipe = $db->pipe_query('--uploaded', $pkg); if ($pipe) { while(<$pipe>) { if (!/^(\S+): Propagating new state /) { $msgs .= $_; } } close($pipe); if ($msgs or $?) { $self->log($msgs) if $msgs; $self->log("wanna-build --uploaded failed with status ", exitstatus($?), "\n" ) if $?; } else { my $archdist_name = "$arch_name/$dist_name"; $self->get('Uploaded Pkgs')->{$archdist_name} .= " $pkg"; } } else { $self->log("Can't spawn wanna-build --uploaded: $!\n"); } } sub upload ($$) { my $self = shift; my $udir = shift; my $upload_to = shift; chdir( "$main::HOME/$udir" ) || return; lock_file( "$main::HOME/$udir" ); my( $f, $g, @before, @after ); foreach $f (<*.changes>) { ($g = $f) =~ s/\.changes$/\.upload/; push( @before, $f ) if ! -f $g; } unlock_file( "$main::HOME/$udir" ); if (!@before) { $self->log("Nothing to do for $udir\n"); return; } $self->log(scalar(@before), " jobs to upload in $udir: @before\n"); foreach $f (@before) { ($g = $f) =~ s/\.changes$/\.upload/; my $logref = $self->do_dupload( $upload_to, $f ); if (defined $logref and scalar(@$logref) > 0) { my $line; foreach $line (@$logref) { $self->log($line); } } if ( -f $g ) { if (!open( F, "<$f" )) { $self->log("Cannot open $f: $!\n"); next; } my $text; { local($/); undef $/; $text = ; } close( F ); if ($text !~ /^Architecture:\s*(.*)\s*$/m) { $self->log("$f doesn't have a Architecture: field\n"); next; } my @archs = split( /\s+/, $1 ); if ($text !~ /^Distribution:\s*(.*)\s*$/m) { $self->log("$f doesn't have a Distribution: field\n"); next; } my @dists = split( /\s+/, $1 ); my ($version,$source,$pkg); if ($text =~ /^Version:\s*(\S+)\s*$/m) { $version = $1; } if ($text =~ /^Source:\s*(\S+)(?:\s+\(\S+\))?\s*$/m) { $source = $1; } if (defined($version) and defined($source)) { $pkg = "${source}_$version"; } else { ($pkg = $f) =~ s/_\S+\.changes$//; } $self->uploaded($pkg, @archs, @dists); } else { push (@after, $f); } } if (@after) { $self->log("The following jobs were not processed (successfully):\n" . "@after\n"); } else { $self->log("dupload successful.\n"); } $self->write_stats("uploads", scalar(@before) - scalar(@after)); } sub do_dupload ($@) { my $self = shift; my $upload_to = shift; my @jobs = @_; my @log; local( *PIPE ); my( $current_job, $current_file, @failed, $errs ); if (!open( PIPE, "dupload -k --to $upload_to @jobs &1 |" )) { return "Cannot spawn dupload: $!"; } my $dup_log = ""; while( ) { $dup_log .= $_; chomp; if (/^\[ job \S+ from (\S+\.changes)$/) { $current_job = $1; } elsif (/^warning: MD5sum mismatch for (\S+), skipping/i) { my $f = $1; push( @log, "dupload error: md5sum mismatch for $f\n" ); $errs .= "md5sum mismatch on file $f ($current_job)\n"; push( @failed, $current_job ); } elsif (/^\[ Uploading job (\S+)$/) { $current_job = "$1.changes"; } elsif (/dupload fatal error: Can't upload (\S+)/i || /^\s(\S+).*scp: (.*)$/) { my($f, $e) = ($1, $2); push( @log, "dupload error: upload error for $f\n" ); push( @log, "($e)\n" ) if $e; $errs .= "upload error on file $f ($current_job)\n"; push( @failed, $current_job ); } elsif (/Timeout at [\S]+ line [\d]+$/) { $errs .= "upload timeout on file $current_job\n"; push( @failed, $current_job ); } elsif (/^\s(\S+)\s+[\d.]+ kB /) { $current_file = $1; } } close( PIPE ); if ($?) { if (($? >> 8) == 141) { push( @log, "dupload error: SIGPIPE (broken connection)\n" ); $errs .= "upload error (broken connection) during ". "file $current_file ($current_job)\n"; push( @failed, $current_job ); } else { push( @log, "dupload exit status ". exitstatus($?) ); $errs .= "dupload exit status ".exitstatus($?)."\n"; push( @failed, $current_job ); } } foreach (@failed) { my $u = $_; $u =~ s/\.changes$/\.upload/; unlink( $u ); push( @log, "Removed $u due to upload errors.\n" ); $errs .= "Removed $u to reupload later.\n"; } if ($errs) { $errs .= "\nComplete output from dupload:\n\n$dup_log"; send_mail($self->get_conf('ADMIN_MAIL'), "dupload errors", $errs); } return \@log; } 1;