summaryrefslogtreecommitdiffstats
path: root/lib/Buildd/Daemon.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Buildd/Daemon.pm')
-rw-r--r--lib/Buildd/Daemon.pm998
1 files changed, 998 insertions, 0 deletions
diff --git a/lib/Buildd/Daemon.pm b/lib/Buildd/Daemon.pm
new file mode 100644
index 0000000..dc07254
--- /dev/null
+++ b/lib/Buildd/Daemon.pm
@@ -0,0 +1,998 @@
+# buildd: daemon to automatically build packages
+# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+# Copyright © 2009 Roger Leigh <rleigh@debian.org>
+# Copyright © 2005 Ryan Murray <rmurray@debian.org>
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+#
+#######################################################################
+
+package Buildd::Daemon;
+
+use strict;
+use warnings;
+
+use POSIX;
+use Buildd qw(isin lock_file unlock_file send_mail exitstatus);
+use Buildd::Conf qw();
+use Buildd::Base;
+use Sbuild qw($devnull df);
+use Sbuild::Sysconfig;
+use Sbuild::ChrootRoot;
+use Buildd::Client;
+use Cwd;
+
+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('Daemon', 0);
+
+ return $self;
+}
+
+sub ST_MTIME () { 9 }
+
+sub run {
+ my $self = shift;
+
+ my $host = Sbuild::ChrootRoot->new($self->get('Config'));
+ $host->set('Log Stream', $self->get('Log Stream'));
+ $self->set('Host', $host);
+ $host->begin_session() or die "Can't begin session\n";
+
+ my $my_binary = $0;
+ $my_binary = cwd . "/" . $my_binary if $my_binary !~ m,^/,;
+ $self->set('MY_BINARY', $my_binary);
+
+ my @bin_stats = stat( $my_binary );
+ die "Cannot stat $my_binary: $!\n" if !@bin_stats;
+ $self->set('MY_BINARY_TIME', $bin_stats[ST_MTIME]);
+
+ chdir( $self->get_conf('HOME') . "/build" )
+ or die "Can't cd to " . $self->get_conf('HOME') . "/build: $!\n";
+
+ open( STDIN, "</dev/null" )
+ or die "$0: can't redirect stdin to /dev/null: $!\n";
+
+ if (open( PID, "<" . $self->get_conf('PIDFILE') )) {
+ my $pid = <PID>;
+ close( PID );
+ $pid =~ /^[[:space:]]*(\d+)/; $pid = $1;
+ if (!$pid || (kill( 0, $pid ) == 0 && $! == ESRCH)) {
+ warn "Removing stale pid file (process $pid dead)\n";
+ }
+ else {
+ die "Another buildd (pid $pid) is already running.\n";
+ }
+ }
+
+ if (!@{$self->get_conf('DISTRIBUTIONS')}) {
+ die "distribution list is empty, aborting.";
+ }
+
+ if (!$self->get_conf('NO_DETACH')) {
+ defined(my $pid = fork) or die "can't fork: $!\n";
+ exit if $pid; # parent exits
+ setsid or die "can't start a new session: $!\n";
+ }
+
+ $self->set('PID', $$); # Needed for cleanup
+ $self->set('Daemon', 1);
+
+ open( PID, ">" . $self->get_conf('PIDFILE') )
+ or die "can't create " . $self->get_conf('PIDFILE') . ": $!\n";
+ printf PID "%5d\n", $self->get('PID');
+ close( PID );
+
+ $self->log("Daemon started. (pid=$$)\n");
+
+ undef $ENV{'DISPLAY'};
+
+# the main loop
+ MAINLOOP:
+ while( 1 ) {
+ $self->check_restart();
+
+ my ( $dist_config, $pkg_ver) = get_next_REDO($self);
+ $self->do_build( $dist_config, $pkg_ver) if $pkg_ver;
+ next MAINLOOP if $pkg_ver;
+
+ ( $dist_config, $pkg_ver) = get_next_WANNABUILD($self);
+ $self->do_build( $dist_config, $pkg_ver) if $pkg_ver;
+ next MAINLOOP if $pkg_ver;
+
+ # sleep a little bit if there was nothing to do this time
+ $self->log("Nothing to do -- sleeping " .
+ $self->get_conf('IDLE_SLEEP_TIME') . " seconds\n");
+ my $idle_start_time = time;
+ sleep( $self->get_conf('IDLE_SLEEP_TIME') );
+ my $idle_end_time = time;
+ $self->write_stats("idle-time", $idle_end_time - $idle_start_time);
+ }
+
+ return 0;
+}
+
+sub get_next_WANNABUILD {
+ my $self = shift;
+ foreach my $dist_config (@{$self->get_conf('DISTRIBUTIONS')}) {
+ $self->check_ssh_master($dist_config);
+ my $dist_name = $dist_config->get('DIST_NAME');
+ my %givenback = $self->read_givenback();
+ my $db = $self->get_db_handle($dist_config);
+ my $pipe = $db->pipe_query(
+ ($dist_config->get('WANNA_BUILD_API') ? '--api '.$dist_config->get('WANNA_BUILD_API') : ''),
+ '--list=needs-build',
+ ($dist_config->get('WANNA_BUILD_MIN_AGE') ? '--min-age '.$dist_config->get('WANNA_BUILD_MIN_AGE') : ''),
+ );
+ if (!$pipe) {
+ $self->log("Can't spawn wanna-build --list=needs-build: $!\n");
+ next MAINLOOP;
+ }
+
+ my($pkg_ver, $total, $nonex, $lowprio_pkg_ver);
+ while( <$pipe> ) {
+ my $socket = $dist_config->get('WANNA_BUILD_SSH_SOCKET');
+ if ($socket &&
+ (/^Couldn't connect to $socket: Connection refused[\r]?$/ ||
+ /^Control socket connect\($socket\): Connection refused[\r]?$/)) {
+ unlink($socket);
+ $self->check_ssh_master($dist_config);
+ next;
+ }
+ elsif (/^Total (\d+) package/) {
+ $total = $1;
+ next;
+ }
+ elsif (/^Database for \S+ doesn.t exist/) {
+ $nonex = 1;
+ }
+ next if $nonex;
+ next if defined($pkg_ver); #we only want one!
+ my @line = (split( /\s+/, $_));
+ my $pv = $line[0];
+ my $no_build_regex = $dist_config->get('NO_BUILD_REGEX');
+ my $build_regex = $dist_config->get('BUILD_REGEX');
+ next if $no_build_regex && $pv =~ m,$no_build_regex,;
+ next if $build_regex && $pv !~ m,$build_regex,;
+ $pv =~ s,^.*/,,;
+ my $p;
+ ($p = $pv) =~ s/_.*$//;
+ next if isin( $p, @{$dist_config->get('NO_AUTO_BUILD')} );
+ next if $givenback{$pv};
+ if (isin( $p, @{$dist_config->get('WEAK_NO_AUTO_BUILD')} )) {
+ # only consider the first lowprio item if there are
+ # multiple ones
+ next if defined($lowprio_pkg_ver);
+ $lowprio_pkg_ver = $pv;
+ next;
+ }
+ $pkg_ver = $pv;
+ }
+ close( $pipe );
+ next if $nonex;
+ if ($?) {
+ $self->log("wanna-build --list=needs-build --dist=${dist_name} failed; status ",
+ exitstatus($?), "\n");
+ next;
+ }
+ $self->log("${dist_name}: total $total packages to build.\n") if defined($total);
+
+ # Build weak_no_auto packages before the next dist
+ if (!defined($pkg_ver) && defined($lowprio_pkg_ver)) {
+ $pkg_ver = $lowprio_pkg_ver;
+ }
+
+ next if !defined($pkg_ver);
+ my $todo = $self->do_wanna_build( $dist_config, $pkg_ver );
+ last if !$todo;
+ return ( $dist_config, $todo );
+ }
+}
+
+sub get_next_REDO {
+ my $self = shift;
+ my ( $dist_config, $pkg_ver);
+ foreach my $current_dist_config (@{$self->get_conf('DISTRIBUTIONS')}) {
+ $pkg_ver = $self->get_from_REDO( $current_dist_config );
+ $dist_config = $current_dist_config;
+ last if defined($pkg_ver);
+ }
+ return ( $dist_config, $pkg_ver);
+}
+
+
+sub get_from_REDO {
+ my $self = shift;
+ my $wanted_dist_config = shift;
+ my $ret = undef;
+ local( *F );
+
+ lock_file( "REDO" );
+ goto end if ! -f "REDO";
+ if (!open( F, "<REDO" )) {
+ $self->log("File REDO exists, but can't open it: $!\n");
+ goto end;
+ }
+ my @lines = <F>;
+ close( F );
+
+ $self->block_signals();
+ if (!open( F, ">REDO" )) {
+ $self->log("Can't open REDO for writing: $!\n",
+ "Raw contents:\n@lines\n");
+ goto end;
+ }
+ foreach (@lines) {
+ if (!/^(\S+)\s+(\S+)(?:\s*|\s+(\d+)\s+(\S.*))?$/) {
+ $self->log("Ignoring/deleting bad line in REDO: $_");
+ next;
+ }
+ my($pkg, $dist, $binNMUver, $changelog) = ($1, $2, $3, $4);
+ if ($dist eq $wanted_dist_config->get('DIST_NAME') && !defined($ret)) {
+ $ret = {'pv' => $pkg };
+ if (defined $binNMUver) {
+ $ret->{'changelog'} = $changelog;
+ $ret->{'binNMU'} = $binNMUver;
+ }
+ } else {
+ print F $_;
+ }
+ }
+ close( F );
+
+ end:
+ unlock_file( "REDO" );
+ $self->unblock_signals();
+ return $ret;
+}
+
+sub add_given_back ($$) {
+ my $self = shift;
+ my $pkg_ver = shift;
+
+ local( *F );
+ lock_file("SBUILD-GIVEN-BACK", 0);
+
+ if (open( F, ">>SBUILD-GIVEN-BACK" )) {
+ print F $pkg_ver . " " . time() . "\n";
+ close( F );
+ } else {
+ $self->log("Can't open SBUILD-GIVEN-BACK: $!\n");
+ }
+
+ unlock_file("SBUILD-GIVEN-BACK");
+}
+
+sub read_givenback {
+ my $self = shift;
+
+ my %gb;
+ my $now = time;
+ local( *F );
+
+ lock_file( "SBUILD-GIVEN-BACK" );
+
+ if (open( F, "<SBUILD-GIVEN-BACK" )) {
+ %gb = map { split } <F>;
+ close( F );
+ }
+
+ if (open( F, ">SBUILD-GIVEN-BACK" )) {
+ foreach (keys %gb) {
+ if ($now - $gb{$_} > $self->get_conf('DELAY_AFTER_GIVE_BACK') *60) {
+ delete $gb{$_};
+ }
+ else {
+ print F "$_ $gb{$_}\n";
+ }
+ }
+ close( F );
+ }
+ else {
+ $self->log("Can't open SBUILD-GIVEN-BACK: $!\n");
+ }
+
+ unlock:
+ unlock_file( "SBUILD-GIVEN-BACK" );
+ return %gb;
+}
+
+sub do_wanna_build {
+ my $self = shift;
+
+ my $dist_config = shift;
+ my $pkgver = shift;
+ my @output = ();
+ my $ret = undef;
+ my $n = 0;
+
+ $self->block_signals();
+
+ my $db = $self->get_db_handle($dist_config);
+ if ($dist_config->get('WANNA_BUILD_API') >= 1) {
+ use YAML::Tiny;
+ my $pipe = $db->pipe_query(
+ '--api '.$dist_config->get('WANNA_BUILD_API'),
+ $pkgver);
+ unless ($pipe) {
+ $self->unblock_signals();
+ $self->log("Can't spawn wanna-build: $!\n");
+ return undef;
+ }
+ local $/ = undef;
+ my $yaml = <$pipe>;
+ $yaml =~ s,^update transactions:.*$,,m; # get rid of simulate output in case simulate is specified above
+ $self->log($yaml);
+ $yaml = YAML::Tiny->read_string($yaml);
+ $yaml = $yaml->[0];
+ foreach my $pkgv (@$yaml) {
+ my $pkg = (keys %$pkgv)[0];
+ my $pkgd;
+ foreach my $k (@{$pkgv->{$pkg}}) {
+ foreach my $l (keys %$k) {
+ $pkgd->{$l} = $k->{$l};
+ }
+ };
+ if ($pkgd->{'status'} ne 'ok') {
+ $self->log("Can't take $pkg: $pkgd->{'status'}\n");
+ next;
+ }
+ $ret = { 'pv' => $pkgver };
+ # fix SHOULD_BUILD_MSGS
+# if ($self->get_conf('SHOULD_BUILD_MSGS')) {
+# $self->handle_prevfailed( $dist_config, grep( /^\Q$pkg\E_/, @_ ) );
+# } else {
+# push( @output, grep( /^\Q$pkg\E_/, @_ ) );
+ my $fields = { 'changelog' => 'extra-changelog',
+ 'binNMU' => 'binNMU',
+ 'extra-depends' => 'extra-depends',
+ 'extra-conflicts' => 'extra-conflicts',
+ 'build_dep_resolver' => 'build_dep_resolver',
+ 'arch_all' => 'arch_all',
+ 'mail_logs' => 'mail_logs',
+ };
+ for my $f (keys %$fields) {
+ $ret->{$f} = $pkgd->{$fields->{$f}} if $pkgd->{$fields->{$f}};
+ }
+ last;
+ }
+ close( $pipe );
+ $self->unblock_signals();
+ $self->write_stats("taken", $n) if $n;
+ return $ret;
+ }
+ my $pipe = $db->pipe_query(
+ '-v',
+ $pkgver);
+ if ($pipe) {
+ while( <$pipe> ) {
+ next if /^wanna-build Revision/;
+ if (/^(\S+):\s*ok/) {
+ $ret = { 'pv' => $pkgver };
+ ++$n;
+ }
+ elsif (/^(\S+):.*NOT OK/) {
+ my $pkg = $1;
+ my $nextline = <$pipe>;
+ chomp( $nextline );
+ $nextline =~ s/^\s+//;
+ $self->log("Can't take $pkg: $nextline\n");
+ }
+ elsif (/^(\S+):.*previous version failed/i) {
+ my $pkg = $1;
+ ++$n;
+ if ($self->get_conf('SHOULD_BUILD_MSGS')) {
+ $self->handle_prevfailed( $dist_config, $pkgver );
+ } else {
+ $ret = { 'pv' => $pkgver };
+ }
+ # skip until ok line
+ while( <$pipe> ) {
+ last if /^\Q$pkg\E:\s*ok/;
+ }
+ }
+ elsif (/^(\S+):.*needs binary NMU (\d+)/) {
+ my $pkg = $1;
+ my $binNMUver = $2;
+ chop (my $changelog = <$pipe>);
+ my $newpkg;
+ ++$n;
+
+ push( @output, grep( /^\Q$pkg\E_/, @_ ) );
+ $ret = { 'pv' => $pkgver };
+ $ret->{'changelog'} = $changelog;
+ $ret->{'binNMU'} = $binNMUver;
+ # skip until ok line
+ while( <$pipe> ) {
+ last if /^\Q$pkg\E:\s*aok/;
+ }
+ }
+ }
+ close( $pipe );
+ $self->unblock_signals();
+ $self->write_stats("taken", $n) if $n;
+ return $ret;
+ }
+ else {
+ $self->unblock_signals();
+ $self->log("Can't spawn wanna-build: $!\n");
+ return undef;
+ }
+}
+
+sub should_skip {
+ my $self = shift;
+ my $pkgv = shift;
+
+ my $found = 0;
+
+ $self->lock_file("SKIP", 0);
+ goto unlock if !open( F, "SKIP" );
+ my @pkgs = <F>;
+ close(F);
+
+ if (!open( F, ">SKIP" )) {
+ $self->log("Can't open SKIP for writing: $!\n",
+ "Would write: @pkgs\nminus $pkgv\n");
+ goto unlock;
+ }
+ foreach (@pkgs) {
+ if (/^\Q$pkgv\E$/) {
+ ++$found;
+ $self->log("$pkgv found in SKIP file -- skipping building it\n");
+ }
+ else {
+ print F $_;
+ }
+ }
+ close( F );
+ unlock:
+ $self->unlock_file("SKIP");
+ return $found;
+}
+
+sub do_build {
+ my $self = shift;
+ my $dist_config = shift;
+ my $todo = shift;
+ # $todo = { 'pv' => $pkg_ver, 'changelog' => $binNMUlog->{$pkg_ver}, 'binNMU' => $binNMUver; };
+
+ # If the package to build is in SKIP, then skip.
+ if ($self->should_skip($todo->{'pv'})) {
+ return;
+ }
+
+ my $free_space;
+
+ while (($free_space = df(".")) < $self->get_conf('MIN_FREE_SPACE')) {
+ $self->log("Delaying build, because free space is low ($free_space KB)\n");
+ my $idle_start_time = time;
+ sleep( 10*60 );
+ my $idle_end_time = time;
+ $self->write_stats("idle-time", $idle_end_time - $idle_start_time);
+ }
+
+ $self->log("Starting build (dist=" . $dist_config->get('DIST_NAME') . ") of "
+ .($todo->{'binNMU'} ? "!".$todo->{'binNMU'}."!" : "")
+ ."$todo->{'pv'}\n");
+ $self->write_stats("builds", 1);
+
+ my @sbuild_args = ();
+ if ($self->get_conf('NICE_LEVEL') != 0) {
+ @sbuild_args = ( 'nice', '-n', $self->get_conf('NICE_LEVEL') );
+ }
+
+ push @sbuild_args, 'sbuild',
+ '--apt-update',
+ '--no-apt-upgrade',
+ '--no-apt-distupgrade',
+ '--no-run-lintian',
+ '--batch',
+ "--stats-dir=" . $self->get_conf('HOME') . "/stats",
+ "--dist=" . $dist_config->get('DIST_NAME');
+
+ push @sbuild_args, "--sbuild-mode=buildd";
+ push @sbuild_args, "--mailfrom=".$dist_config->get('MAILFROM') if $dist_config->get('MAILFROM');
+ push @sbuild_args, "--maintainer=".$dist_config->get('MAINTAINER_NAME') if $dist_config->get('MAINTAINER_NAME');
+ push @sbuild_args, "--dpkg-file-suffix=".$self->get_conf('DPKG_FILE_SUFFIX') if $self->get_conf('DPKG_FILE_SUFFIX');
+
+ if ($dist_config->get('SIGN_WITH')) {
+ push @sbuild_args, '--keyid=' . $dist_config->get('SIGN_WITH');
+ }
+
+ #multi-archive-buildd keeps the mailto configuration in the builddrc, so
+ #this needs to be passed over to sbuild. If the buildd config doesn't have
+ #it, we hope that the address is configured in .sbuildrc and the right one:
+ if ($dist_config->get('LOGS_MAILED_TO')) {
+ push @sbuild_args, '--mail-log-to=' . $dist_config->get('LOGS_MAILED_TO');
+ } elsif ($dist_config->get('LOGS_MAIL_ALSO') || $todo->{'mail_logs'}) {
+ push @sbuild_args, '--mail-log-to=' . join (',', grep { $_ } ($dist_config->get('LOGS_MAIL_ALSO'), $todo->{'mail_logs'}));
+ }
+ #Some distributions (bpo, experimental) require a more complex dep resolver.
+ #Ask sbuild to use another build-dep resolver if the config says so:
+ if ($dist_config->get('BUILD_DEP_RESOLVER') || $todo->{'build_dep_resolver'}) {
+ push @sbuild_args, '--build-dep-resolver=' . ($dist_config->get('BUILD_DEP_RESOLVER') || $todo->{'build_dep_resolver'});
+ }
+ if ($dist_config->get('BUILT_ARCHITECTURE')) {
+ if ($dist_config->get('BUILT_ARCHITECTURE') eq 'all') {
+ push ( @sbuild_args, "--arch-all", "--no-arch-any" );
+ } else {
+ push ( @sbuild_args, "--no-arch-all", "--arch-any", "--arch=" . $dist_config->get('BUILT_ARCHITECTURE') );
+ }
+ }
+ push ( @sbuild_args, "--chroot=" . $dist_config->get('SBUILD_CHROOT') )
+ if $dist_config->get('SBUILD_CHROOT');
+
+
+ push ( @sbuild_args, "--binNMU=$todo->{'binNMU'}") if $todo->{'binNMU'};
+ push ( @sbuild_args, "--make-binNMU=$todo->{'changelog'}") if $todo->{'changelog'};
+ push ( @sbuild_args, "--add-conflicts=$todo->{'extra-conflicts'}") if $todo->{'extra-conflicts'};
+ push ( @sbuild_args, "--add-depends=$todo->{'extra-depends'}") if $todo->{'extra-depends'};
+ push @sbuild_args, $todo->{'pv'};
+ $self->log("command line: @sbuild_args\n");
+
+ $main::sbuild_pid = open(SBUILD_OUT, "-|");
+
+ #We're childish, so call sbuild:
+ if ($main::sbuild_pid == 0) {
+ { exec (@sbuild_args) };
+ $self->log("Cannot execute sbuild: $!\n");
+ exit(64);
+ }
+
+ if (!defined $main::sbuild_pid) {
+ $self->log("Cannot fork for sbuild: $!\n");
+ goto failed;
+ }
+
+ #We want to collect the first few lines of sbuild output:
+ my ($sbuild_output_line_count, @sbuild_output_buffer) = (0, ());
+ while (<SBUILD_OUT>) {
+ #5 lines are enough:
+ if (++$sbuild_output_line_count < 5) {
+ push @sbuild_output_buffer, $_;
+ }
+ }
+
+ #We got enough output, now just wait for sbuild to die:
+ my $rc;
+ while (($rc = wait) != $main::sbuild_pid) {
+ if ($rc == -1) {
+ last if $! == ECHILD;
+ next if $! == EINTR;
+ $self->log("wait for sbuild: $!; continuing to wait\n");
+ } elsif ($rc != $main::sbuild_pid) {
+ $self->log("wait for sbuild: returned unexpected pid $rc\n");
+ }
+ }
+ my $sbuild_exit_code = $?;
+ undef $main::sbuild_pid;
+ close(SBUILD_OUT);
+
+ #Process sbuild's results:
+ my $db = $self->get_db_handle($dist_config);
+ my $failed = 1;
+ my $giveback = 1;
+
+ if (WIFEXITED($sbuild_exit_code)) {
+ my $status = WEXITSTATUS($sbuild_exit_code);
+
+ if ($status == 0) {
+ $failed = 0;
+ $giveback = 0;
+ $self->log("sbuild of $todo->{'pv'} succeeded -- marking as built in wanna-build\n");
+ $db->run_query('--built', '--dist=' . $dist_config->get('DIST_NAME'), $todo->{'pv'});
+
+ if ($dist_config->get('SIGN_WITH') && $dist_config->get('BUILT_ARCHITECTURE')) {
+ # XXX: Check if signature is present.
+ $self->move_to_upload($dist_config, $todo->{'pv'}, $todo->{'binNMU'});
+ }
+ } elsif ($status == 2) {
+ $giveback = 0;
+ $self->log("sbuild of $todo->{'pv'} failed with status $status (build failed) -- marking as attempted in wanna-build\n");
+ $db->run_query('--attempted', '--dist=' . $dist_config->get('DIST_NAME'), $todo->{'pv'});
+ $self->write_stats("failed", 1);
+ } else {
+ $self->log("sbuild of $todo->{'pv'} failed with status $status (local problem) -- giving back\n");
+ }
+ } elsif (WIFSIGNALED($sbuild_exit_code)) {
+ my $sig = WTERMSIG($sbuild_exit_code);
+ $self->log("sbuild of $todo->{'pv'} failed with signal $sig (local problem) -- giving back\n");
+ } else {
+ $self->log("sbuild of $todo->{'pv'} failed with unknown reason (local problem) -- giving back\n");
+ }
+
+ if ($giveback) {
+ $db->run_query('--give-back', '--dist=' . $dist_config->get('DIST_NAME'), $todo->{'pv'});
+ $self->add_given_back($todo->{'pv'});
+ $self->write_stats("give-back", 1);
+ }
+
+ # Check if we encountered some local error to stop further building
+ if ($giveback) {
+ if (!defined $main::sbuild_fails) {
+ $main::sbuild_fails = 0;
+ }
+
+ $main::sbuild_fails++;
+
+ if ($main::sbuild_fails > $self->get_conf('MAX_SBUILD_FAILS')) {
+ $self->log("sbuild now failed $main::sbuild_fails times in ".
+ "a row; going to sleep\n");
+ send_mail( $self->get_conf('ADMIN_MAIL'),
+ "Repeated mess with sbuild",
+ <<EOF );
+The execution of sbuild now failed for $main::sbuild_fails times.
+These are the first $sbuild_output_line_count lines of the last failed sbuild call:
+@sbuild_output_buffer
+
+The daemon is going to sleep for 1 hour, or can be restarted with SIGUSR2.
+EOF
+ my $oldsig;
+ eval <<'EOF';
+$oldsig = $SIG{'USR2'};
+$SIG{'USR2'} = sub ($) { die "signal\n" };
+my $idle_start_time = time;
+sleep( 60*60 );
+my $idle_end_time = time;
+$SIG{'USR2'} = $oldsig;
+$self->write_stats("idle-time", $idle_end_time - $idle_start_time);
+EOF
+ $main::sbuild_fails = 0;
+ }
+ }
+ else {
+ # Either a build success or an attempted build will cause the
+ # counter to reset.
+ $main::sbuild_fails = 0;
+ }
+ $self->log("Build finished.\n");
+}
+
+sub move_to_upload {
+ my $self = shift;
+ my $dist_config = shift;
+ my $pv = shift;
+ my $binNMUver = shift;
+
+ my $arch = $dist_config->get('BUILT_ARCHITECTURE');
+ my $upload_dir = $dist_config->get('DUPLOAD_LOCAL_QUEUE_DIR');
+ my $file_suffix = $self->get_conf('DPKG_FILE_SUFFIX');
+
+ if ($binNMUver) {
+ $pv .= '+b' . $binNMUver;
+ }
+
+ my $pkg_noepoch = $pv;
+ $pkg_noepoch =~ s/_\d*:/_/;
+
+ my $changes_name = $pkg_noepoch . '_' . $arch . $file_suffix . '.changes';
+ my $upload_path = $self->get_conf('HOME') . '/' . $dist_config->get('DUPLOAD_LOCAL_QUEUE_DIR') . '/' . $pkg_noepoch . '_' . $arch . '.upload';
+
+ $self->log("$pv is autosigned, moving to '$upload_dir'\n");
+ if ( -f $upload_path ) {
+ unlink( $upload_path );
+ $self->log("'$upload_path' removed.\n");
+ }
+ system(qw(dcmd mv --),
+ sprintf('%s/build/%s', $self->get_conf('HOME'), $changes_name),
+ sprintf('%s/%s/', $self->get_conf('HOME'), $dist_config->get('DUPLOAD_LOCAL_QUEUE_DIR'))
+ );
+ $self->log("$pv moved to '$upload_dir'\n");
+}
+
+sub handle_prevfailed {
+ my $self = shift;
+ my $dist_config = shift;
+ my $pkgv = shift;
+
+ my $dist_name = $dist_config->get('DIST_NAME');
+ my( $pkg, $fail_msg, $changelog);
+
+ $self->log("$pkgv previously failed -- asking admin first\n");
+ ($pkg = $pkgv) =~ s/_.*$//;
+
+ my $db = $self->get_db_handle($dist_config);
+ my $pipe = $db->pipe_query(
+ '--info',
+ $pkg);
+ if (!$pipe) {
+ $self->log("Can't run wanna-build: $!\n");
+ return;
+ }
+
+ $fail_msg = "";
+ while (<$pipe>) {
+ $fail_msg .= $_;
+ }
+
+ close($pipe);
+ if ($?) {
+ $self->log("wanna-build exited with error $?\n");
+ return;
+ }
+
+ send_mail( $self->get_conf('ADMIN_MAIL'),
+ "Should I build $pkgv (dist=${dist_name})?",
+ "The package $pkg failed to build in a previous version. ".
+ "The fail\n".
+ "messages are:\n\n$fail_msg\n".
+ "Should buildd try to build the new version, or should it ".
+ "fail with the\n".
+ "same messages again.? Please answer with 'build' (or 'ok'), ".
+ "or 'fail'.\n" );
+}
+
+sub get_changelog {
+ # This method is currently broken. It makes some assumptions about source
+ # layout that are no longer true. Furthermore it tries fetching through
+ # the host instead of creating a session (which is necessary for snapshot-
+ # based chroots) and work in the chroot.
+
+ my $self = shift;
+ my $dist_config = shift;
+ my $pkg = shift;
+
+ my $dist_name = $dist_config->get('DIST_NAME');
+ my $changelog = "";
+ my $analyze = "";
+ my $chroot_apt_options;
+ my $file;
+ my $retried = 0;
+
+ $pkg =~ /^([\w\d.+-]+)_([\w\d:.~+-]+)/;
+ my ($n, $v) = ($1, $2);
+ (my $v_ne = $v) =~ s/^\d+://;
+ my $pkg_ne = "${n}_${v_ne}";
+
+retry:
+ my @schroot = ($self->get_conf('SCHROOT'), '-c',
+ $dist_name . '-' . $self->get_conf('ARCH') . '-sbuild', '--');
+ my @schroot_root = ($self->get_conf('SCHROOT'), '-c',
+ $dist_name . '-' . $self->get_conf('ARCH') . '-sbuild',
+ '-u', 'root', '--');
+ my $apt_get = $self->get_conf('APT_GET');
+
+ my $pipe = $self->get('Host')->pipe_command(
+ { COMMAND => [@schroot,
+ "$apt_get", '-q', '-d',
+ '--diff-only', 'source', "$n=$v"],
+ USER => $self->get_conf('USERNAME'),
+ PRIORITY => 0,
+ });
+ if (!$pipe) {
+ $self->log("Can't run schroot: $!\n");
+ return;
+ }
+
+ my $msg = "";
+ while (<$pipe>) {
+ $msg .= $_;
+ }
+
+ close($pipe);
+
+ if ($? == 0 && $msg !~ /get 0B/) {
+ $analyze = "diff";
+ $file = "${n}_${v_ne}.diff.gz";
+ }
+
+ if (!$analyze) {
+ my $pipe2 = $self->get('Host')->pipe_command(
+ { COMMAND => [@schroot,
+ "$apt_get", '-q', '-d',
+ '--tar-only', 'source', "$n=$v"],
+ USER => $self->get_conf('USERNAME'),
+ PRIORITY => 0,
+ });
+ if (!$pipe2) {
+ $self->log("Can't run schroot: $!\n");
+ return;
+ }
+
+ my $msg = <$pipe2>;
+
+ close($pipe2);
+
+ if ($? == 0 && $msg !~ /get 0B/) {
+ $analyze = "tar";
+ $file = "${n}_${v_ne}.tar.gz";
+ }
+ }
+
+ if (!$analyze && !$retried) {
+ $self->get('Host')->run_command(
+ { COMMAND => [@schroot_root,
+ $apt_get, '-qq',
+ 'update'],
+ USER => $self->get_conf('USERNAME'),
+ PRIORITY => 0,
+ STREAMOUT => $devnull
+ });
+
+ $retried = 1;
+ goto retry;
+ }
+
+ return "ERROR: cannot find any source" if !$analyze;
+
+ if ($analyze eq "diff") {
+ if (!open( F, "gzip -dc '$file' 2>/dev/null |" )) {
+ return "ERROR: Cannot spawn gzip to zcat $file: $!";
+ }
+ while( <F> ) {
+ # look for header line of a file */debian/changelog
+ last if m,^\+\+\+\s+[^/]+/debian/changelog(\s+|$),;
+ }
+ while( <F> ) {
+ last if /^---/; # end of control changelog patch
+ next if /^\@\@/;
+ $changelog .= "$1\n" if /^\+(.*)$/;
+ last if /^\+\s+--\s+/;
+ }
+ while( <F> ) { } # read to end of file to avoid broken pipe
+ close( F );
+ if ($?) {
+ return "ERROR: error status ".exitstatus($?)." from gzip on $file";
+ }
+ unlink( $file );
+ }
+ elsif ($analyze eq "tar") {
+ if (!open( F, "tar -xzOf '$file' '*/debian/changelog' ".
+ "2>/dev/null |" )) {
+ return "ERROR: Cannot spawn tar for $file: $!";
+ }
+ while( <F> ) {
+ $changelog .= $_;
+ last if /^\s+--\s+/;
+ }
+ while( <F> ) { } # read to end of file to avoid broken pipe
+ close( F );
+ if ($?) {
+ return "ERROR: error status ".exitstatus($?)." from tar on $file";
+ }
+ unlink( $file );
+ }
+
+ return $changelog;
+}
+
+sub check_restart {
+ my $self = shift;
+ my @stats = stat( $self->get('MY_BINARY') );
+
+ if (@stats && $self->get('MY_BINARY_TIME') != $stats[ST_MTIME]) {
+ $self->log("My binary has been updated -- restarting myself (pid=$$)\n");
+ unlink( $self->get_conf('PIDFILE') );
+ kill ( 15, $main::ssh_pid ) if $main::ssh_pid;
+ exec $self->get('MY_BINARY');
+ }
+
+ if ( -f $self->get_conf('HOME') . "/EXIT-DAEMON-PLEASE" ) {
+ unlink($self->get_conf('HOME') . "/EXIT-DAEMON-PLEASE");
+ $self->shutdown("NONE (flag file exit)");
+ }
+}
+
+sub block_signals {
+ my $self = shift;
+
+ POSIX::sigprocmask( SIG_BLOCK, $main::block_sigset );
+}
+
+sub unblock_signals {
+ my $self = shift;
+
+ POSIX::sigprocmask( SIG_UNBLOCK, $main::block_sigset );
+}
+
+sub check_ssh_master {
+ my $self = shift;
+ my $dist_config = shift;
+
+ my $ssh_socket = $dist_config->get('WANNA_BUILD_SSH_SOCKET');
+
+ return 1 if (!$ssh_socket);
+ return 1 if ( -S $ssh_socket );
+
+ my $ssh_master_pids = {};
+ if ($self->get('SSH_MASTER_PIDS')) {
+ $ssh_master_pids = $self->get('SSH_MASTER_PIDS');
+ } else {
+ $self->set('SSH_MASTER_PIDS', $ssh_master_pids);
+ }
+
+ if ($ssh_master_pids->{$ssh_socket})
+ {
+ my $wpid = waitpid ( $ssh_master_pids->{$ssh_socket}, WNOHANG );
+ return 1 if ($wpid != -1 and $wpid != $ssh_master_pids->{$ssh_socket});
+ }
+
+ my $new_master_pid = fork;
+
+ #We are in the newly forked child:
+ if (defined($new_master_pid) && $new_master_pid == 0) {
+ exec (@{$dist_config->get('WANNA_BUILD_SSH_CMD')}, "-MN");
+ }
+
+ #We are the parent:
+ if (!defined $new_master_pid) {
+ $self->log("Cannot fork for ssh master: $!\n");
+ return 0;
+ }
+
+ $ssh_master_pids->{$ssh_socket} = $new_master_pid;
+
+ while ( ! -S $ssh_socket )
+ {
+ sleep 1;
+ my $wpid = waitpid ( $new_master_pid, WNOHANG );
+ return 0 if ($wpid == -1 or $wpid == $new_master_pid);
+ }
+ return 1;
+}
+
+sub shutdown {
+ my $self = shift;
+ my $signame = shift;
+
+ $self->log("buildd ($$) received SIG$signame -- shutting down\n");
+
+ if ($self->get('SSH_MASTER_PIDS')) {
+ my $ssh_master_pids = $self->get('SSH_MASTER_PIDS');
+ for my $ssh_socket (keys %{$ssh_master_pids}) {
+ my $master_pid = $ssh_master_pids->{$ssh_socket};
+ kill ( 15, $master_pid );
+ delete ( $ssh_master_pids->{$ssh_socket} );
+ }
+ }
+
+ if (defined $main::sbuild_pid) {
+ $self->log("Killing sbuild (pid=$main::sbuild_pid)\n");
+ kill( 15, $main::sbuild_pid );
+ $self->log("Waiting max. 2 minutes for sbuild to finish\n");
+ $SIG{'ALRM'} = sub ($) { die "timeout\n"; };
+ alarm( 120 );
+ eval "waitpid( $main::sbuild_pid, 0 )";
+ alarm( 0 );
+ if ($@) {
+ $self->log("sbuild did not die!");
+ }
+ else {
+ $self->log("sbuild died normally");
+ }
+ unlink( "SBUILD-REDO-DUMPED" );
+ }
+ unlink( $self->get('Config')->get('PIDFILE') );
+ $self->log("exiting now\n");
+ $self->close_log();
+ exit 1;
+}
+
+1;