21
22
#######################################################################
24
($main::HOME = $ENV{'HOME'})
25
or die "HOME not defined in environment!\n";
27
use Buildd qw(open_log close_log);
36
sub get_from_REDO ($\%);
37
sub read_givenback ();
38
sub do_wanna_build ($\%@);
40
sub handle_prevfailed ($$);
41
sub get_changelog ($$);
43
sub append_to_REDO ($$@);
48
sub unblock_signals ();
49
sub check_ssh_master ();
53
my @distlist = qw(oldstable-security stable-security testing-security stable testing unstable);
55
$my_binary = cwd . "/" . $my_binary if $my_binary !~ m,^/,;
56
my @bin_stats = stat( $my_binary );
57
die "Cannot stat $my_binary: $!\n" if !@bin_stats;
58
my $my_bin_time = $bin_stats[ST_MTIME];
61
chdir( "$main::HOME/build" )
62
or die "Can't cd to $main::HOME/build: $!\n";
64
open( STDIN, "</dev/null" )
65
or die "$0: can't redirect stdin to /dev/null: $!\n";
67
if (open( PID, "<buildd.pid" )) {
70
$pid =~ /^(\d+)/; $pid = $1;
71
if (!$pid || (kill( 0, $pid ) == 0 && $! == ESRCH)) {
72
warn "Removing stale pid file (process $pid dead)\n";
75
die "Another buildd (pid $pid) is already running.\n";
79
# Initialise the configuration.
82
@Buildd::Conf::take_from_dists
83
or die "take_from_dists is empty, aborting.";
85
defined(my $pid = fork) or die "can't fork: $!\n";
86
exit if $pid; # parent exits
87
setsid or die "can't start a new session: $!\n";
89
open( PID, ">buildd.pid" )
90
or die "can't create buildd.pid: $!\n";
91
printf PID "%5d\n", $$;
93
END { unlink( "buildd.pid" ) if (defined($pid) and $pid == 0); }
97
logger( "Daemon started. (pid=$$)\n" );
30
use Sbuild::OptionsBase;
32
sub shutdown_fast ($);
34
sub reread_config ($);
37
my $conf = Buildd::Conf->new();
38
exit 1 if !defined($conf);
39
my $options = Sbuild::OptionsBase->new($conf, "buildd", "1");
40
exit 1 if !defined($options);
41
my $daemon = Buildd::Daemon->new($conf);
42
exit 1 if !defined($daemon);
44
my $log = open_log($daemon->get('Config'));
45
$daemon->set('Log Stream', $log);
47
# Global signal handling
99
48
foreach (qw(QUIT ILL TRAP ABRT BUS FPE USR2 SEGV PIPE XCPU XFSZ PWR)) {
102
logger( "buildd ($$) killed by SIG$signame\n" );
103
unlink( "buildd.pid" );
49
$SIG{$_} = \&shutdown_fast;
107
51
$SIG{'HUP'} = \&reopen_log;
52
$SIG{'USR1'} = \&reread_config;
108
53
$SIG{'INT'} = \&shutdown;
109
54
$SIG{'TERM'} = \&shutdown;
110
undef $ENV{'DISPLAY'};
114
MAINLOOP: while( 1 ) {
116
Buildd::Conf::check_reread_config();
124
foreach $dist (@Buildd::Conf::take_from_dists) {
126
Buildd::Conf::check_reread_config();
127
my @redo = get_from_REDO( $dist, %binNMUlog );
129
do_build( $dist, %binNMUlog, @redo );
133
} while( $thisdone );
135
foreach $dist (@Buildd::Conf::take_from_dists) {
137
Buildd::Conf::check_reread_config();
138
my %givenback = read_givenback();
139
if (!open( PIPE, "$Buildd::Conf::sshcmd wanna-build --list=needs-build --dist=$dist ".
140
($Buildd::Conf::wanna_build_dbbase ?
141
"--database=$Buildd::Conf::wanna_build_dbbase ":"").
142
($Buildd::Conf::wanna_build_user?"--user=$Buildd::Conf::wanna_build_user " : "")."2>&1 |" )) {
143
logger( "Can't spawn wanna-build --list=needs-build: $!\n" );
146
my(@todo, $total, $nonex, @lowprio_todo, $max_build);
147
$max_build = $Buildd::Conf::max_build;
149
if ($Buildd::Conf::sshsocket and (/^Couldn't connect to $Buildd::Conf::sshsocket: Connection refused[\r]?$/ or
150
/^Control socket connect\($Buildd::Conf::sshsocket\): Connection refused[\r]?$/)) {
151
unlink( $Buildd::Conf::sshsocket );
154
elsif (/^Total (\d+) package/) {
158
elsif (/^Database for \S+ doesn.t exist/) {
162
next if @todo >= $max_build;
163
my @line = (split( /\s+/, $_));
165
next if $Buildd::Conf::no_build_regex && $pv =~ m,$Buildd::Conf::no_build_regex,;
166
next if $Buildd::Conf::build_regex && $pv !~ m,$Buildd::Conf::build_regex,;
169
($p = $pv) =~ s/_.*$//;
170
next if Buildd::isin( $p, @Buildd::Conf::no_auto_build );
171
next if $givenback{$pv};
172
if (Buildd::isin( $p, @Buildd::Conf::weak_no_auto_build )) {
173
push( @lowprio_todo, $pv );
176
if ($line[1] =~ /:binNMU/) {
185
logger( "wanna-build --list=needs-build --dist=$dist failed; status ",
186
exitstatus($?), "\n" );
189
logger( "$dist: total $total packages to build.\n" ) if $total;
190
if ($total && $Buildd::Conf::secondary_daemon_threshold &&
191
$total < $Buildd::Conf::secondary_daemon_threshold) {
192
logger( "Not enough packages to build -- ".
193
"secondary daemon not starting\n" );
197
# Build weak_no_auto packages before the next dist
198
if (!@todo && @lowprio_todo) {
199
push ( @todo, @lowprio_todo );
203
@todo = do_wanna_build( $dist, %binNMUlog, @todo );
205
do_build( $dist, %binNMUlog, @todo );
210
# sleep a little bit if there was nothing to do this time
212
logger( "Nothing to do -- sleeping $Buildd::Conf::idle_sleep_time seconds\n" );
213
my $idle_start_time = time;
214
sleep( $Buildd::Conf::idle_sleep_time );
215
my $idle_end_time = time;
216
write_stats( "idle-time", $idle_end_time - $idle_start_time );
220
sub get_from_REDO ($\%) {
221
my $wanted_dist = shift;
222
my $binNMUlog = shift;
227
goto end if ! -f "REDO";
228
if (!open( F, "<REDO" )) {
229
logger( "File REDO exists, but can't open it: $!\n" );
236
if (!open( F, ">REDO" )) {
237
logger( "Can't open REDO for writing: $!\n",
238
"Raw contents:\n@lines\n" );
241
my $max_build = $Buildd::Conf::max_build;
243
if (!/^(\S+)\s+(\S+)(?:\s*|\s+(\d+)\s+(\S.*))?$/) {
244
logger( "Ignoring/deleting bad line in REDO: $_" );
247
my($pkg, $dist, $binNMUver, $changelog) = ($1, $2, $3, $4);
248
if ($dist eq $wanted_dist && @redo < $max_build) {
249
if (defined $binNMUver) {
250
if (scalar(@redo) == 0) {
251
$binNMUlog->{$pkg} = $changelog;
252
push( @redo, "!$binNMUver!$pkg" );
256
$max_build = scalar(@redo);
268
unlock_file( "REDO" );
273
sub read_givenback () {
278
lock_file( "SBUILD-GIVEN-BACK" );
280
if (open( F, "<SBUILD-GIVEN-BACK" )) {
281
%gb = map { split } <F>;
285
if (open( F, ">SBUILD-GIVEN-BACK" )) {
287
if ($now - $gb{$_} > $Buildd::Conf::delay_after_give_back*60) {
291
print F "$_ $gb{$_}\n";
297
logger( "Can't open SBUILD-GIVEN-BACK: $!\n" );
301
unlock_file( "SBUILD-GIVEN-BACK" );
305
sub do_wanna_build ($\%@) {
307
my $binNMUlog = shift;
313
if (open( PIPE, "$Buildd::Conf::sshcmd wanna-build -v --no-down-propagation ".
314
($Buildd::Conf::wanna_build_dbbase?"--database=$Buildd::Conf::wanna_build_dbbase ":"").
315
($Buildd::Conf::wanna_build_user?"--user=$Buildd::Conf::wanna_build_user ":"").
316
"--dist=$dist @_ 2>&1 |" )) {
318
next if /^wanna-build Revision/;
319
if (/^(\S+):\s*ok/) {
321
push( @output, grep( /^\Q$pkg\E_/, @_ ) );
324
elsif (/^(\S+):.*NOT OK/) {
326
my $nextline = <PIPE>;
328
$nextline =~ s/^\s+//;
329
logger( "Can't take $pkg: $nextline\n" );
331
elsif (/^(\S+):.*previous version failed/i) {
334
if ($Buildd::Conf::should_build_msgs) {
335
handle_prevfailed( $dist, grep( /^\Q$pkg\E_/, @_ ) );
337
push( @output, grep( /^\Q$pkg\E_/, @_ ) );
341
last if /^\Q$pkg\E:\s*ok/;
344
elsif (/^(\S+):.*needs binary NMU (\d+)/) {
347
chop (my $changelog = <PIPE>);
351
push( @output, grep( /^\Q$pkg\E_/, @_ ) );
352
$binNMUlog->{$output[$#output]} = $changelog;
353
$output[$#output] = "!$binNMUver!" . $output[$#output];
356
last if /^\Q$pkg\E:\s*aok/;
362
write_stats( "taken", $n ) if $n;
367
logger( "Can't spawn wanna-build: $!\n" );
372
sub do_build ($\%@) {
374
my $binNMUlog = shift;
378
while (($free_space = df(".")) < $Buildd::Conf::min_free_space) {
379
logger( "Delaying build, because free space is low ($free_space KB)\n" );
380
my $idle_start_time = time;
382
my $idle_end_time = time;
383
write_stats( "idle-time", $idle_end_time - $idle_start_time );
386
logger( "Starting build (dist=$dist) of:\n@_\n" );
387
write_stats( "builds", scalar(@_) );
390
my @sbuild_args = ( 'nice', '-n', "$Buildd::Conf::nice_level", 'sbuild',
393
"--stats-dir=$main::HOME/stats",
395
my $sbuild_gb = '--auto-give-back';
396
if ($Buildd::Conf::sshcmd) {
398
$sbuild_gb .= "$Buildd::Conf::sshsocket\@" if $Buildd::Conf::sshsocket;
399
$sbuild_gb .= "$Buildd::Conf::wanna_build_user\@" if $Buildd::Conf::wanna_build_user;
400
$sbuild_gb .= "$main::sshuser\@" if $main::sshuser;
401
$sbuild_gb .= "$main::sshhost";
403
# Otherwise newer sbuild will take the package name as an --auto-give-back
404
# parameter (changed from regexp to GetOpt::Long parsing)
407
push ( @sbuild_args, $sbuild_gb );
408
push ( @sbuild_args, "--database=$Buildd::Conf::wanna_build_dbbase" )
409
if $Buildd::Conf::wanna_build_dbbase;
411
if (scalar(@_) == 1 and $_[0] =~ s/^!(\d+)!//) {
414
push ( @sbuild_args, "--binNMU=$binNMUver", "--make-binNMU=" . $binNMUlog->{$_[0]});
416
logger( "command line: @sbuild_args @_\n" );
418
if (($main::sbuild_pid = fork) == 0) {
419
{ exec (@sbuild_args, @_) };
420
logger( "Cannot execute sbuild: $!\n" );
424
if (!defined $main::sbuild_pid) {
425
logger( "Cannot fork for sbuild: $!\n" );
429
while (($rc = wait) != $main::sbuild_pid) {
431
last if $! == ECHILD;
433
logger( "wait for sbuild: $!; continuing to wait\n" );
434
} elsif ($rc != $main::sbuild_pid) {
435
logger( "wait for sbuild: returned unexpected pid $rc\n" );
438
undef $main::sbuild_pid;
441
logger( "sbuild failed with status ".exitstatus($?)."\n" );
443
if (-f "SBUILD-REDO-DUMPED") {
444
logger( "Found SBUILD-REDO-DUMPED; sbuild already dumped ",
445
"pkgs which need rebuiling/\n" );
449
while( <F> ) { ++$n; }
451
write_stats( "builds", -$n );
453
elsif (-f "SBUILD-FINISHED") {
454
my @finished = read_FINISHED();
455
logger( "sbuild has already finished:\n@finished\n" );
458
push( @unfinished, $_ ) if !Buildd::isin( $_, @finished );
460
logger( "Adding rest to REDO:\n@unfinished\n" );
461
append_to_REDO( $dist, '', @unfinished );
462
write_stats( "builds", -scalar(@unfinished) );
465
if (defined $binNMUver) {
466
logger( "Assuming binNMU failed and adding to REDO:\n@_\n" );
467
append_to_REDO( $dist, "$binNMUver $binNMUlog->{$_[0]}", @_ );
469
logger( "Assuming all packages unbuilt and adding to REDO:\n@_\n" );
470
append_to_REDO( $dist, '', @_ );
472
write_stats( "builds", -scalar(@_) );
475
delete $binNMUlog->{$_[0]} if defined $binNMUver;
477
if (++$main::sbuild_fails > 2) {
478
logger( "sbuild now failed $main::sbuild_fails times in ".
479
"a row; going to sleep\n" );
480
send_mail( $Buildd::Conf::admin_mail,
481
"Repeated mess with sbuild",
483
The execution of sbuild now failed for $main::sbuild_fails times.
484
Something must be wrong here...
486
The daemon is going to sleep for 1 hour, or can be restarted with SIGUSR2.
490
$oldsig = $SIG{'USR2'};
491
$SIG{'USR2'} = sub ($) { die "signal\n" };
492
my $idle_start_time = time;
494
my $idle_end_time = time;
495
$SIG{'USR2'} = $oldsig;
496
write_stats( "idle-time", $idle_end_time - $idle_start_time );
501
$main::sbuild_fails = 0;
503
unlink "SBUILD-REDO-DUMPED" if -f "SBUILD-REDO-DUMPED";
504
logger( "Build finished.\n" );
507
sub handle_prevfailed ($$) {
510
my( $pkg, $fail_msg, $changelog, $fail_cmd);
512
logger( "$pkgv previously failed -- asking admin first\n" );
513
($pkg = $pkgv) =~ s/_.*$//;
514
$fail_cmd = "$Buildd::Conf::sshcmd wanna-build ".($Buildd::Conf::wanna_build_dbbase?
515
"--database=$Buildd::Conf::wanna_build_dbbase ":""). "--info --dist=$dist $pkg";
516
$fail_msg = `$fail_cmd`;
519
local $SIG{'ALRM'} = sub ($) { die "Timeout!\n" };
520
eval { $changelog = get_changelog( $dist, $pkgv ) };
522
$changelog = "ERROR: FTP timeout" if $@;
524
send_mail( $Buildd::Conf::admin_mail,
525
"Should I build $pkgv (dist=$dist)?",
526
"The package $pkg failed to build in a previous version. ".
528
"messages are:\n\n$fail_msg\n".
529
($changelog !~ /^ERROR/ ?
530
"The changelog entry for the newest version is:\n\n".
532
"Sorry, the last changelog entry could not be extracted:\n".
534
"Should buildd try to build the new version, or should it ".
536
"same messages again.? Please answer with 'build' (or 'ok'), ".
540
sub get_changelog ($$) {
545
my $chroot_apt_options;
551
$pkg =~ /^([\w\d.+-]+)_([\w\d:.~+-]+)/;
552
my ($n, $v) = ($1, $2);
553
(my $v_ne = $v) =~ s/^\d+://;
554
my $pkg_ne = "${n}_${v_ne}";
557
my $schroot = "$Buildd::Conf::schroot -c $dist-$Buildd::Conf::arch-sbuild --";
558
my $schroot_root = "$Buildd::Conf::schroot -c $dist-$Buildd::Conf::arch-sbuild -u root --";
559
$msg = `$schroot $Buildd::Conf::apt_get -q -d --diff-only source $n=$v 2>&1`;
560
if ($? == 0 && $msg !~ /get 0B/) {
562
$file = "${n}_${v_ne}.diff.gz";
566
$msg = `$schroot $Buildd::Conf::apt_get -q -d --tar-only source $n=$v 2>&1`;
567
if ($? == 0 && $msg !~ /get 0B/) {
569
$file = "${n}_${v_ne}.tar.gz";
573
if (!$analyze && !$retried) {
574
system "$schroot_root $Buildd::Conf::apt_get ".
575
"-qq update &>/dev/null";
580
return "ERROR: cannot find any source" if !$analyze;
582
if ($analyze eq "diff") {
583
if (!open( F, "gzip -dc '$file' 2>/dev/null |" )) {
584
return "ERROR: Cannot spawn gzip to zcat $file: $!";
587
# look for header line of a file */debian/changelog
588
last if m,^\+\+\+\s+[^/]+/debian/changelog(\s+|$),;
591
last if /^---/; # end of control changelog patch
593
$changelog .= "$1\n" if /^\+(.*)$/;
594
last if /^\+\s+--\s+/;
596
while( <F> ) { } # read to end of file to avoid broken pipe
599
return "ERROR: error status ".exitstatus($?)." from gzip on $file";
603
elsif ($analyze eq "tar") {
604
if (!open( F, "tar -xzOf '$file' '*/debian/changelog' ".
606
return "ERROR: Cannot spawn tar for $file: $!";
612
while( <F> ) { } # read to end of file to avoid broken pipe
615
return "ERROR: error status ".exitstatus($?)." from tar on $file";
623
# TODO: Merge with sbuild function
627
my $free = `/bin/df $dir | tail -1`;
628
my @free = split( /\s+/, $free );
632
sub append_to_REDO ($$@) {
643
if (open( F, "REDO" )) {
648
if (open( F, ">>REDO" )) {
649
foreach $pkg (@npkgs) {
650
next if grep( /^\Q$pkg\E\s/, @pkgs );
651
print F "$pkg ${dist}$postfix\n";
656
logger( "Can't open REDO: $!\n" );
660
unlock_file( "REDO" );
664
sub read_FINISHED () {
668
if (!open( F, "<SBUILD-FINISHED" )) {
669
logger( "Can't open SBUILD-FINISHED: $!\n" );
672
chomp( @pkgs = <F> );
674
unlink( "SBUILD-FINISHED" );
58
sub shutdown_fast ($) {
60
$daemon->log("buildd ($$) killed by SIG$signame\n")
62
unlink( $conf->get('PIDFILE') );
678
66
sub shutdown ($) {
679
67
my $signame = shift;
681
logger( "buildd ($$) received SIG$signame -- shutting down\n" );
69
$daemon->log("buildd ($$) received SIG$signame -- shutting down\n")
682
72
if (defined $main::ssh_pid) {
683
73
kill ( 15, $main::ssh_pid );
685
75
if (defined $main::sbuild_pid) {
686
logger( "Killing sbuild (pid=$main::sbuild_pid)\n" );
76
$daemon->log("Killing sbuild (pid=$main::sbuild_pid)\n")
687
78
kill( 15, $main::sbuild_pid );
688
logger( "Waiting max. 2 minutes for sbuild to finish\n" );
79
$daemon->log("Waiting max. 2 minutes for sbuild to finish\n")
689
81
$SIG{'ALRM'} = sub ($) { die "timeout\n"; };
691
83
eval "waitpid( $main::sbuild_pid, 0 )";
694
logger( "sbuild did not die!" );
86
$daemon->log("sbuild did not die!")
697
logger( "sbuild died normally" );
90
$daemon->log("sbuild died normally")
699
93
unlink( "SBUILD-REDO-DUMPED" );
701
unlink( "buildd.pid" );
702
logger( "exiting now\n" );
95
unlink( $conf->get('PIDFILE') );
96
$daemon->log("exiting now\n");
707
sub check_restart () {
708
my @stats = stat( $my_binary );
710
if (@stats && $my_bin_time != $stats[ST_MTIME]) {
711
logger( "My binary has been updated -- restarting myself (pid=$$)\n" );
712
unlink( "buildd.pid" );
713
kill ( 15, $main::ssh_pid ) if $main::ssh_pid;
717
if ( -f "$main::HOME/EXIT-DAEMON-PLEASE" ) {
718
unlink("$main::HOME/EXIT-DAEMON-PLEASE");
719
&shutdown("NONE (flag file exit)");
723
sub block_signals () {
724
POSIX::sigprocmask( SIG_BLOCK, $main::block_sigset );
727
sub unblock_signals () {
728
POSIX::sigprocmask( SIG_UNBLOCK, $main::block_sigset );
731
sub check_ssh_master () {
732
return 1 if (!$Buildd::Conf::sshsocket);
733
return 1 if ( -S $Buildd::Conf::sshsocket );
737
my $wpid = waitpid ( $main::ssh_pid, WNOHANG );
738
return 1 if ($wpid != -1 and $wpid != $main::ssh_pid);
741
($main::ssh_pid = fork)
742
or exec "$Buildd::Conf::sshcmd -MN";
744
if (!defined $main::ssh_pid) {
745
logger( "Cannot fork for ssh master: $!\n" );
749
while ( ! -S $Buildd::Conf::sshsocket )
752
my $wpid = waitpid ( $main::ssh_pid, WNOHANG );
753
return 0 if ($wpid == -1 or $wpid == $main::ssh_pid);
101
sub reread_config ($) {
104
$daemon->log("buildd ($$) received SIG$signame -- rereading configuration\n")
107
$Buildd::Conf::reread_config = 1;
113
$daemon->log("buildd ($$) received SIG$signame -- reopening logfile\n")
116
Buildd::reopen_log($conf);
120
unlink( $conf->get('PIDFILE') )
121
if (defined($conf) &&
123
$daemon->get('Daemon'));