[backend] define armvXl architecture but keep armvXel and hl for compatibility
[opensuse:build-service.git] / src / backend / bs_worker
1 #!/usr/bin/perl -w
2 #
3 # Copyright (c) 2006-2009 Michael Schroeder, Novell Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as
7 # published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program (see the file COPYING); if not, write to the
16 # Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 #
19 ################################################################
20 #
21 # Worker build process. Builds jobs received from a Repository Server,
22 # sends build binary packages back.
23 #
24
25 BEGIN {
26   my ($wd) = $0 =~ m-(.*)/- ;
27   $wd ||= '.';
28   unshift @INC,  "$wd/build";
29   unshift @INC,  "$wd";
30 }
31
32 use Digest::MD5 ();
33 use XML::Structured ':bytes';
34 use Data::Dumper;
35 use POSIX;
36 use Fcntl qw(:DEFAULT :flock);
37 BEGIN { Fcntl->import(':seek') unless defined &SEEK_SET; }
38
39 use Storable;
40
41 use BSRPC;
42 use BSServer;
43 use BSConfig;
44 use BSUtil;
45 use BSXML;
46 use BSKiwiXML;
47 use BSHTTP;
48 use BSBuild;
49
50 use strict;
51
52 my $buildroot;
53 my $port;
54 my $statedir;
55 my $hostarch;
56 my $vm = '';
57 my $vm_tmpfs_mode;
58 my $vm_root = '';
59 my $vm_swap = '';
60 my $vm_kernel;
61 my $vm_initrd;
62 my $vm_memory;
63 my $vmdisk_rootsize;
64 my $vmdisk_swapsize;
65 my $vmdisk_filesystem;
66 my $workerid;
67 my $srcserver;
68 my @reposervers;
69 my $testmode;
70 my $noworkercheck;
71 my $nobuildcodecheck;
72 my $oneshot;
73 my $silent;
74 my $hostcheck;
75 my $localkiwi;
76
77 my $jobs;
78 my $cachedir;
79 my $cachesize;
80
81 my $buildlog_maxsize = 500 * 1000000;
82 my $buildlog_maxidle = 8 * 3600;
83 my $xenstore_maxsize = 20 * 1000000;
84 my $gettimeout = 3600; # 1 hour timeout to avoid forever hanging workers
85
86 #FIXME 3.0: obsolete the not exiting arm architectures
87 my %cando = (
88   'armv4l'  => [                                          'armv4l'                                                                                      ],
89   'armv5l'  => [                                          'armv4l', 'armv5l'                    , 'armv5el'                                             ],
90   'armv6l'  => [                                          'armv4l', 'armv5l', 'armv6l'          , 'armv5el', 'armv6el'                                  ],
91   'armv7l'  => [                                          'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el' ],
92   'sh4'     => [                                                                                                                        'sh4' ],
93   'i586'    => [          'i586',                         'armv4l', 'armv5l', 'armv6l', 'armv7l', 'mips', 'mipsel', 'sh4' ],
94   'i686'    => [          'i586',         'i686',         'armv4l', 'armv5l', 'armv6l', 'armv7l', 'mips', 'mipsel', 'sh4' ],
95   'x86_64'  => ['x86_64', 'i586:linux32', 'i686:linux32', 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'mips', 'mipsel', 'sh4' ],
96 #
97   'parisc'  => ['hppa', 'hppa64:linux64'],
98   'parisc64'=> ['hppa64', 'hppa:linux32'],
99   'ppc'     => ['ppc'],
100   'ppc64'   => ['ppc64', 'ppc:powerpc32'],
101   'ia64'    => ['ia64'],
102   's390'    => ['s390'],
103   's390x'   => ['s390x', 's390:s390'],
104   'sparc'   => ['sparcv8', 'sparc'],
105   'sparc64' => ['sparc64v', 'sparc64', 'sparcv9v', 'sparcv9', 'sparcv8:linux32', 'sparc:linux32'],
106   'mips'    => ['mips'],
107   'mips64'  => ['mips64', 'mips:mips32'],
108 );
109
110 $hostcheck = $BSConfig::workerhostcheck if defined($BSConfig::workerhostcheck);
111
112 sub lockstate {
113   while (1) {
114     open(STATELOCK, '>>', "$statedir/state") || die("$statedir/state: $!\n");
115     flock(STATELOCK, LOCK_EX) || die("flock $statedir/state: $!\n");
116     my @s = stat(STATELOCK);
117     last if $s[3];      # check nlink
118     close(STATELOCK);   # race, try again
119   }
120   my $oldstate = readxml("$statedir/state", $BSXML::workerstate, 1);
121   $oldstate = {} unless $oldstate;
122   return $oldstate;
123 }
124
125 sub unlockstate {
126   close(STATELOCK);
127 }
128
129 sub commitstate {
130   my ($newstate) = @_;
131   writexml("$statedir/state.new", "$statedir/state", $newstate, $BSXML::workerstate) if $newstate;
132   close(STATELOCK);
133 }
134
135 sub trunc_logfile {
136   my $lf = shift;
137   open(LF, "<$lf") || return; 
138   my $buf;
139   sysread(LF, $buf, 1000000);
140   $buf .= "\n\n[truncated]\n\n";
141   sysseek(LF, -1000000, 2);
142   sysread(LF, $buf, 1000000, length($buf));
143   close LF;
144   $buf .= "\nLogfile got too big, killed job.\n";
145   open(LF, ">$lf.new") || return; 
146   syswrite(LF, $buf);
147   close LF;
148   rename("$lf.new", $lf);
149 }
150
151 sub cleanup_job {
152   if ($vm_tmpfs_mode) {
153     qsystem("umount", $buildroot) && die("umount tmpfs failed: $!\n");
154   }
155 }
156
157 sub kill_job {
158   if (system("$statedir/build/build", "--root", $buildroot, ($vm ? ($vm, "$vm_root") : ()), "--kill")) {
159     return 0;
160   }
161   cleanup_job();
162   return 1;
163 }
164
165 sub usage {
166   my ($ret) = @_;
167
168 print <<EOF;
169 Usage: $0 [OPTION] --root <directory> --statedir <directory>
170
171        --root      : buildroot directory
172
173        --port      : fixed port number
174
175        --statedir  : state directory
176
177        --id        : worker id
178
179        --reposerver: define reposerver, can be used multiple times
180
181        --arch      : define hostarch (overrides 'uname -m')
182                      currently supported architectures: 
183                      @{[sort keys %cando]}
184
185        --kvm       : enable kvm
186
187        --xen       : enable xen
188
189        --lxc       : enable lxc
190
191        --tmpfs     : uses tmpfs (memory) for for the build root
192
193        --device    : set kvm or xen root device (default is <root>/root file)
194
195        --swap      : set kvm or xen swap device (default is <root>/swap file)
196
197        --vm-kernel : set kernel to use (xen/kvm)
198
199        --vm-initrd : set initrd to use (xen/kvm)
200
201        --vm-memory : set amount of memory to use (xen/kvm)
202
203        --vmdisk-rootsize <size>
204                    : size of the root disk image (default 4096M)
205
206        --vmdisk-swapsize <size>
207                    : size of the swap disk image (default 1024M)
208
209        --vmdisk-filesystem <none|ext3|ext4>
210                    : filesystem to use for autosetup root disk image
211
212        --test      : enable test mode
213
214        --build     : just build the package, don't send anything back
215                      (needs a buildinfo file as argument)
216
217        --noworkerupdate
218                    : do not check if the worker is up-to-date
219
220        --nobuildupdate
221                    : do not check if the build code is up-to-date
222
223        --nocodeupdate
224                    : do not update both worker and build code
225
226        --jobs <nr> : hand over the number of parallel jobs to build
227
228        --oneshot <seconds>
229                    : just build one package, do not wait more then
230                      <seconds> seconds if nothing is available
231
232        --hostcheck <hostcheck>
233                    : call to check if the host can build the package
234
235        --cachedir <cachedir>
236        --cachesize <size_in_mb>
237                    : use cachedir to cache fetched binaries
238
239        --help      : this message
240
241 EOF
242   exit $ret || 0;
243 }
244
245 my @saveargv = @ARGV;   # so we can restart ourself
246 my $justbuild;
247 my $exitrestart;
248
249 my $exitrestart_timeout = 300;  # wait max 5 minuntes
250
251 exit(0) if @ARGV == 1 && $ARGV[0] eq '--selftest';
252
253 while (@ARGV) {
254   usage(0) if $ARGV[0] eq '--help';
255   if ($ARGV[0] eq '--exit' || $ARGV[0] eq '--stop') {
256     shift @ARGV;
257     $exitrestart = 'exit';
258     next;
259   }
260   if ($ARGV[0] eq '--restart') {
261     shift @ARGV;
262     $exitrestart = 'restart';
263     next;
264   }
265   if ($ARGV[0] eq '--root') {
266     shift @ARGV;
267     $buildroot = shift @ARGV;
268     next;
269   }
270   if ($ARGV[0] eq '--port') {
271     shift @ARGV;
272     $port = shift @ARGV;
273     next;
274   }
275   if ($ARGV[0] eq '--arch') {
276     shift @ARGV;
277     $hostarch = shift @ARGV;
278     next;
279   }
280   if ($ARGV[0] eq '--statedir') {
281     shift @ARGV;
282     $statedir = shift @ARGV;
283     next;
284   }
285   if ($ARGV[0] eq '--srcserver') {
286     # default value used if buildinfo does not contain srcserver element
287     shift @ARGV;
288     $srcserver = shift @ARGV;
289     next;
290   }
291   if ($ARGV[0] eq '--reposerver') {
292     shift @ARGV;
293     my $server = shift @ARGV;
294     push @reposervers, $server unless grep {$_ eq $server} @reposervers;
295     next;
296   }
297   if ($ARGV[0] eq '--id') {
298     shift @ARGV;
299     $workerid = shift @ARGV;
300     next;
301   }
302   if ($ARGV[0] eq '--test') {
303     shift @ARGV;
304     $testmode = 1;
305     next;
306   }
307   if ($ARGV[0] eq '--kvm') {
308     $vm = ' --kvm';
309     shift @ARGV;
310     next;
311   }
312   if ($ARGV[0] eq '--xen') {
313     $vm = ' --xen';
314     shift @ARGV;
315     next;
316   }
317   if ($ARGV[0] eq '--lxc') {
318     $vm = 'lxc';
319     shift @ARGV;
320     next;
321   }
322   if ($ARGV[0] eq '--tmpfs') {
323     shift @ARGV;
324     $vm_tmpfs_mode = 1;
325     next;
326   }
327   if ($ARGV[0] eq '--xendevice' || $ARGV[0] eq '--device') {
328     shift @ARGV;
329     $vm_root = shift @ARGV;
330     next;
331   }
332   if ($ARGV[0] eq '--xenswap' || $ARGV[0] eq '--swap') {
333     shift @ARGV;
334     $vm_swap = shift @ARGV;
335     next;
336   }
337   if ($ARGV[0] eq '--vm-kernel') {
338     shift @ARGV;
339     $vm_kernel = shift @ARGV;
340     next;
341   }
342   if ($ARGV[0] eq '--vm-initrd') {
343     shift @ARGV;
344     $vm_initrd = shift @ARGV;
345     next;
346   }
347   if ($ARGV[0] eq '--vm-memory') {
348     shift @ARGV;
349     $vm_memory = shift @ARGV;
350     next;
351   }
352   if ($ARGV[0] eq '--vmdisk-rootsize') {
353     shift @ARGV;
354     $vmdisk_rootsize = shift @ARGV;
355     next;
356   }
357   if ($ARGV[0] eq '--vmdisk-swapsize') {
358     shift @ARGV;
359     $vmdisk_swapsize = shift @ARGV;
360     next;
361   }
362   if ($ARGV[0] eq '--vmdisk-filesystem') {
363     shift @ARGV;
364     $vmdisk_filesystem = shift @ARGV;
365     next;
366   }
367   if ($ARGV[0] eq '--build') {
368     shift @ARGV;
369     $justbuild = 1;
370     next;
371   }
372   if ($ARGV[0] eq '--oneshot') {
373     shift @ARGV;
374     $oneshot = shift @ARGV;
375     next;
376   }
377   if ($ARGV[0] eq '--hostcheck') {
378     shift @ARGV;
379     $hostcheck = shift @ARGV;
380     next;
381   }
382   if ($ARGV[0] eq '--nocodeupdate') {
383     shift @ARGV;
384     $noworkercheck = 1;
385     $nobuildcodecheck = 1;
386     next;
387   }
388   if ($ARGV[0] eq '--noworkerupdate') {
389     shift @ARGV;
390     $noworkercheck = 1;
391     next;
392   }
393   if ($ARGV[0] eq '--nobuildupdate') {
394     shift @ARGV;
395     $nobuildcodecheck = 1;
396     next;
397   }
398   if ($ARGV[0] eq '--silent') {
399     shift @ARGV;
400     $silent= 1;
401     next;
402   }
403   if ($ARGV[0] eq '--localkiwi') {
404     shift @ARGV;
405     $localkiwi = shift @ARGV;
406     next;
407   }
408   if ($ARGV[0] eq '--jobs') {
409     shift @ARGV;
410     $jobs = shift @ARGV;
411     next;
412   }
413   if ($ARGV[0] eq '--cachedir') {
414     shift @ARGV;
415     $cachedir = shift @ARGV;
416     next;
417   }
418   if ($ARGV[0] eq '--cachesize') {
419     shift @ARGV;
420     $cachesize = shift @ARGV;
421     $cachesize *= 1024*1024;
422     next;
423   }
424   last;
425 }
426
427 if ($exitrestart) {
428   usage(1) unless $statedir;
429   if ((! -e "$statedir/lock") || BSUtil::lockcheck('>>', "$statedir/lock")) {
430     die("worker not running.\n") if $exitrestart eq 'restart';
431     print "worker not running.\n";
432     exit(0);
433   }
434   my $state = lockstate();
435   $state->{'state'} = 'idle' unless $state->{'state'};
436   if ($state->{'state'} eq 'building' || $state->{'state'} eq 'killed' || $state->{'state'} eq 'discarded') {
437     $state->{'state'} = 'discarded';
438     $state->{'nextstate'} = $exitrestart eq 'exit' ? 'exit' : 'rebooting';
439   } else {
440     $state->{'state'} = $exitrestart eq 'exit' ? 'exit' : 'rebooting';
441   }
442   commitstate($state);
443   # now wait till the state changes...
444   while(1) {
445     select(undef, undef, undef, .1);
446     if (defined($exitrestart_timeout)) {
447       $exitrestart_timeout -= .1;
448       die("timeout reached, worker not responding\n") if $exitrestart_timeout < 0;
449     }
450     my $curstate = readxml("$statedir/state", $BSXML::workerstate, 1);
451     last unless $curstate && $curstate->{'state'};
452     next if $curstate->{'nextstate'};
453     last if $curstate->{'state'} ne 'exit' && $curstate->{'state'} ne 'rebooting';
454   }
455   exit(0);
456 }
457
458 usage(1) unless $buildroot && $statedir;
459 usage(1) if ($cachedir && !$cachesize) || ($cachesize && !$cachedir);
460
461 $vm_root = "$buildroot/root" unless $vm_root;
462 $vm_swap = "$buildroot/swap" unless $vm_swap;
463
464 # here's the build code we want to use
465 $::ENV{'BUILD_DIR'} = "$statedir/build";
466
467 if (!$hostarch) {
468   $hostarch = `uname -m`;
469   chomp $hostarch;
470   die("could not determine hostarch\n") unless $hostarch;
471 }
472
473 die("arch $hostarch cannot build anything!\n") unless $cando{$hostarch} || ($hostarch eq 'local' && $localkiwi);
474
475 $srcserver = $BSConfig::srcserver unless defined $srcserver;
476 @reposervers = @BSConfig::reposervers unless @reposervers;
477
478 if ($justbuild) {
479   my $buildinfo = readxml($ARGV[0], $BSXML::buildinfo);
480   if ($localkiwi) {
481     # make sure this is the right job for us
482     die("not a kiwi job\n") unless $buildinfo->{'file'} =~ /\.kiwi$/;
483     die("not a kiwi product job\n") unless $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product';
484   }
485   $| = 1;
486
487   dobuild($buildinfo);
488   exit(0);
489 }
490
491 sub stream_logfile {
492   my ($nostream, $start, $end) = @_;
493   open(F, "<$buildroot/.build.log") || die("$buildroot/.build.log: $!\n");
494   my @s = stat(F);
495   $start ||= 0;
496   if (defined($end)) {
497     $end -= $start;
498     die("end is smaller than start\n") if $end < 0;
499   }
500   die("Logfile is not that big\n") if $s[7] < abs($start);
501   defined(sysseek(F, $start, $start < 0 ? Fcntl::SEEK_END : Fcntl::SEEK_SET)) || die("sysseek: $!\n");
502
503   BSServer::reply(undef, 'Content-Type: text/plain', 'Transfer-Encoding: chunked');
504   my $pos = sysseek(F, 0, Fcntl::SEEK_CUR) || die("sysseek: $!\n");
505   while(!defined($end) || $end) {
506     @s = stat(F);
507     if ($s[7] <= $pos) {
508       last if !$s[3];
509       select(undef, undef, undef, .5);
510       next;
511     }
512     my $data = '';
513     my $l = $s[7] - $pos;
514     $l = 4096 if $l > 4096;
515     sysread(F, $data, $l);
516     next unless length($data);
517     $data = substr($data, 0, $end) if defined($end) && length($data) > $end;
518     $pos += length($data);
519     $end -= length($data) if defined $end;
520     $data = sprintf("%X\r\n", length($data)).$data."\r\n";
521     BSServer::swrite($data);
522     last if $nostream && $pos >= $s[7];
523   }
524   close F;
525   BSServer::swrite("0\r\n\r\n");
526 }
527
528 sub send_state {
529   my ($state, $p, $ba, $exclude) = @_;
530   my @args = ("state=$state", "arch=$ba", "port=$p");
531   push @args, "workerid=$workerid" if defined $workerid;
532   for my $server (@reposervers) {
533     next if $exclude && $server eq $exclude;
534     if ($state eq 'idle' && @reposervers > 1) {
535       my $curstate = readxml("$statedir/state", $BSXML::workerstate, 1);
536       last if $curstate && $curstate->{'state'} ne $state;
537     }
538     eval {
539       BSRPC::rpc({
540         'uri' => "$server/worker",
541         'timeout' => 3,
542       }, undef, @args);
543     };
544     print "send_state $server: $@" if $@;
545   }
546 }
547
548 sub codemd5 {
549   my ($dir) = @_;
550   my @files = ls($dir);
551   my $md5 = '';
552   for my $file (sort @files) {
553     next if -l "$dir/$file" || -d "$dir/$file";
554     $md5 .= Digest::MD5::md5_hex(readstr("$dir/$file"))."  $file\n";
555   }
556   $md5 = Digest::MD5::md5_hex($md5);
557   return $md5;
558 }
559
560 sub getcode {
561   my ($dir, $uri, $ineval) = @_;
562
563   # evalize ourself
564   if (!$ineval) {
565     my $md5;
566     eval {
567      $md5 = getcode($dir, $uri, 1);
568     };
569     if ($@) {
570       warn($@);
571       return '';
572     }
573     return $md5;
574   }
575
576   my $ndir = "$dir.new";
577   my $odir = "$dir.old";
578
579   # clean up stale runs
580   if (-e $ndir) {
581     unlink("$ndir/$_") for ls($ndir);
582     rmdir($ndir) || die("rmdir $ndir: $!\n");
583   }
584   if (-e $odir) {
585     unlink("$odir/$_") for ls($odir);
586     rmdir($odir) || die("rmdir $odir: $!\n");
587   }
588
589   mkdir($ndir) || die("mkdir $ndir: $!\n");
590   my $res = BSRPC::rpc({
591     'uri' => $uri,
592     'directory' => $ndir,
593     'timeout' => $gettimeout,
594     'withmd5' => 1,
595     'receiver' => \&BSHTTP::cpio_receiver,
596   });
597   die("getcode error\n") unless $res;
598
599   # got everything, clean things up, check if it really works
600   if ($dir eq 'worker') {
601     symlink('.', "$ndir/XML") || die("symlink: $!\n");
602     chmod(0755, "$ndir/bs_worker");
603     die("bs_worker selftest failed\n") if system("cd $ndir && ./bs_worker --selftest");
604   } elsif ($dir eq 'build') {
605     symlink('.', "$ndir/Build") || die("symlink: $!\n");
606     symlink('.', "$ndir/Date") || die("symlink: $!\n");
607     symlink('.', "$ndir/Time") || die("symlink: $!\n");
608     # we just change everyfile to be on the safe side
609     chmod(0755, "$ndir/$_->{'name'}") for @$res;
610   }
611
612   # ok, commit
613   if (-e $dir) {
614     rename($dir, $odir) || die("rename $dir $odir: $!\n");
615   }
616   rename($ndir, $dir) || die("rename $ndir $dir: $!\n");
617   if (-e $odir) {
618     unlink("$odir/$_") for ls($odir);
619     rmdir($odir);
620   }
621   my $md5 = '';
622   for my $file (sort {$a->{'name'} cmp $b->{'name'}} @$res) {
623     $md5 .= "$file->{'md5'}  $file->{'name'}\n";
624   }
625   $md5 = Digest::MD5::md5_hex($md5);
626   return $md5;
627 }
628
629
630 sub rm_rf {
631   my ($dir) = @_;
632   BSUtil::cleandir($dir);
633   rmdir($dir);
634   die("rmdir $dir failed\n") if -d $dir;
635 }
636
637 sub getsources {
638   my ($buildinfo, $dir) = @_;
639
640   my @meta;
641   push @meta, "$buildinfo->{'srcmd5'}  $buildinfo->{'package'}";
642   my $server = $buildinfo->{'srcserver'} || $srcserver;
643
644   my $res = BSRPC::rpc({
645     'uri' => "$server/getsources",
646     'directory' => $dir,
647     'timeout' => $gettimeout,
648     'withmd5' => 1,
649     'receiver' => \&BSHTTP::cpio_receiver,
650   }, undef, "project=$buildinfo->{'project'}", "package=$buildinfo->{'package'}", "srcmd5=$buildinfo->{'srcmd5'}");
651   die("Error\n") unless ref($res) eq 'ARRAY';
652   if (-e "$dir/.errors") {
653     my $errors = readstr("$dir/.errors", 1);
654     die("getsources: $errors");
655   }
656   # verify sources
657   my %res = map {$_->{'name'} => $_} @$res;
658   my $md5 = '';
659   my @f = ls($dir);
660   for my $f (sort @f) {
661     die("unexpected file: $f") unless $res{$f};
662     $md5 .= "$res{$f}->{'md5'}  $f\n";
663   }
664   $md5 = Digest::MD5::md5_hex($md5);
665   die("source verification fails: $md5 != $buildinfo->{'verifymd5'}\n") if $md5 ne $buildinfo->{'verifymd5'};
666
667   return @meta unless $buildinfo->{'file'} =~ /\.kiwi$/;
668
669   # get additional kiwi sources
670   my @sdep = grep {($_->{'repoarch'} || '') eq 'src'} @{$buildinfo->{'bdep'} || []};
671   for my $src (@sdep) {
672     print "$src->{'name'}, ";
673     my $idir = "$src->{'project'}/$src->{'package'}";
674     $idir = "$dir/images/$idir";
675     mkdir_p($idir);
676     my $res = BSRPC::rpc({
677       'uri' => "$server/getsources",
678       'directory' => $idir,
679       'timeout' => $gettimeout,
680       'withmd5' => 1,
681       'receiver' => \&BSHTTP::cpio_receiver,
682     }, undef, "project=$src->{'project'}", "package=$src->{'package'}", "srcmd5=$src->{'srcmd5'}");
683     die("Error\n") unless ref($res) eq 'ARRAY';
684     if (-e "$idir/.errors") {
685       my $errors = readstr("$idir/.errors", 1);
686       die("getsources: $errors");
687     }
688     push @meta, "$src->{'srcmd5'}  $src->{'project'}/$src->{'package'}";
689   }
690   return @meta;
691 }
692
693 sub getdeltasources {
694   my ($buildinfo, $dir) = @_;
695
696   my @meta;
697   push @meta, "$buildinfo->{'srcmd5'}  $buildinfo->{'package'}";
698   my $server = $buildinfo->{'reposerver'};
699   my $res = BSRPC::rpc({
700     'uri' => "$server/getjobdata",
701     'directory' => $dir,
702     'timeout' => $gettimeout,
703     'receiver' => \&BSHTTP::cpio_receiver,
704   }, undef, "job=$buildinfo->{'job'}", "arch=$buildinfo->{'arch'}", "jobid=$buildinfo->{'jobid'}");
705   die("Error\n") unless ref($res) eq 'ARRAY';
706   return @meta;
707 }
708
709 sub qsystem {
710   my (@args) = @_;
711
712   my $pid;
713   if (!($pid = xfork())) {
714     $SIG{'PIPE'} = 'DEFAULT';
715     open(STDOUT, ">/dev/null") if $silent;
716     exec(@args);
717     die("$args[0]: $!\n"); 
718   }
719   waitpid($pid, 0) == $pid || die("waitpid $pid: $!\n"); 
720   return $?;
721 }
722
723 sub link_or_copy {
724   my ($from, $to) = @_;
725   return 1 if link($from, $to);
726   local *F;
727   local *G;
728   return undef unless open(F, '<', $from);
729   if (!open(G, '>', $to)) {
730     close F;
731     return undef;
732   }
733   my $buf;
734   while (sysread(F, $buf, 8192)) {
735     (syswrite(G, $buf) || 0) == length($buf) || die("$to write: $!\n");
736   }
737   close(F);
738   if (!close(G)) {
739     unlink($to);
740     return undef;
741   }
742   return 1;
743 }
744
745 sub manage_cache {
746   my ($prunesize, $cacheold, $cachenew) = @_;
747   # get the lock
748   local *F;
749   BSUtil::lockopen(\*F, '+>>', "$cachedir/content", 1) || return;
750   my $content;
751   if (-s F) {
752     seek(F, 0, 0);
753     $content = Storable::fd_retrieve(\*F);
754   }
755   $content ||= [];
756   my %content = map {$_->[0] => $_->[1]} @$content;
757   # put cacheold, cachenew at the top
758   if ($cacheold && @$cacheold) {
759     splice(@$content, 0, 0, @$cacheold);
760     $content{$_->[0]} = $_->[1] for @$cacheold;
761   }
762   if ($cachenew) {
763     for my $c (reverse @$cachenew) {
764       my $path = pop(@$c);
765       my $cacheid = $c->[0];
766       my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
767       mkdir_p("$cachedir/".substr($cacheid, 0, 2));
768       unlink("$cachefile.$$");
769       next unless link_or_copy($path, "$cachefile.$$");
770       rename("$cachefile.$$", $cachefile) || die("rename $cachefile.$$ $cachefile: $!\n");
771       if ($path =~ /^(.*)\.(?:deb|rpm)$/) {
772         my $mpath = "$1.meta";
773         if (-s $mpath) {
774           unlink("$cachefile.meta.$$");
775           if (link_or_copy($mpath, "$cachefile.meta.$$")) {
776             rename("$cachefile.meta.$$", "$cachefile.meta") || die("rename $cachefile.meta.$$ $cachefile.meta: $!\n");
777           } else {
778             unlink("$cachefile.meta");
779           }
780         } else {
781           unlink("$cachefile.meta");
782         }
783       }
784       unshift @$content, $c;
785       $content{$c->[0]} = $c->[1];
786     }
787   }
788   # prune cache
789   for my $c (@$content) {
790     if (!defined delete $content{$c->[0]}) {
791       $c = undef;
792       next;
793     }
794     $prunesize -= $c->[1];
795     if ($prunesize < 0) {
796       my $cacheid = $c->[0];
797       my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
798       unlink($cachefile);
799       unlink("$cachefile.meta");
800       $c = undef;
801       next;
802     }
803   }
804   @$content = grep {defined $_} @$content;
805   Storable::nstore($content, "$cachedir/content.new");
806   rename("$cachedir/content.new", "$cachedir/content") || die("rename $cachedir/content.new $cachedir/content");
807   close F;
808 }
809
810 sub getbinaries_cache {
811   my ($dir, $server, $projid, $repoid, $arch, $nometa, $bins) = @_;
812
813   if (!defined &Build::queryhdrmd5) {
814     unshift @INC, "$statedir/build";
815     require Build;
816     Build->import();
817   }
818   if (! -d $dir) {
819     mkdir_p($dir) || die("mkdir_p $dir: $!\n");
820   }
821   my %ret;
822   my $bvl;
823   if ($cachedir) {
824     my @args;
825     push @args, "project=$projid";
826     push @args, "repository=$repoid";
827     push @args, "arch=$arch";
828     push @args, "nometa" if $nometa;
829     push @args, "binaries=".join(',', @$bins);
830     eval {
831       $bvl = BSRPC::rpc({
832         'uri' => "$server/getbinaryversions",
833         'timeout' => $gettimeout,
834         }, $BSXML::binaryversionlist, @args);
835     };
836     warn($@) if $@;
837   }
838   $bvl ||= {};
839   my %bv;
840   for (@{$bvl->{'binary'} || []}) {
841     if ($_->{'error'}) {
842       $bv{$_->{'name'}} = $_;
843     } else {
844       next unless $_->{'name'} =~ /(.*)\.(?:rpm|deb)$/;
845       $bv{$1} = $_;
846     }
847   }
848   my @downloadbins;
849   my $downloadsizek = 0;
850   my @cacheold;
851   my @cachenew;
852   for my $bin (@$bins) {
853     my $bv = $bv{$bin};
854     if (!$bv) {
855       push @downloadbins, $bin;
856       next;
857     }
858     next if $bv->{'error'};
859     my $cacheid =  Digest::MD5::md5_hex("$projid/$repoid/$arch/$bv->{'hdrmd5'}");
860     my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
861     my $usecache = 0;
862     my $havemeta;
863     if (link_or_copy($cachefile, "$dir/$bv->{'name'}")) {
864       my @s = stat("$dir/$bv->{'name'}");
865       die unless @s;
866       if (!$nometa) {
867         my $mn = $bv->{'name'};
868         $mn =~ s/\.(?:rpm|deb)/.meta/;
869         if (link_or_copy("$cachefile.meta", "$dir/$mn")) {
870           local *F;
871           open(F, '<', "$dir/$mn") || die;
872           my $ctx = Digest::MD5->new;
873           $ctx->addfile(*F);
874           close F;
875           if ($ctx->hexdigest() eq $bv->{'metamd5'}) {
876             $usecache = 1;
877             $havemeta = 1;
878           } else {
879             unlink("$dir/$mn");
880           }
881         }
882       } else {
883         $usecache = 1;
884       }
885       if ($usecache) {
886         # check hdrmd5 to be sure we got the right bin
887         my $id = Build::queryhdrmd5("$dir/$bv->{'name'}");
888         $usecache = 0 if ($id || '') ne $bv->{'hdrmd5'};
889       }
890       if (!$usecache) {
891         unlink("$dir/$bv->{'name'}");
892       } else {
893         push @cacheold, [$cacheid, $s[7]];
894       }
895     }
896     if (!$usecache) {
897       push @downloadbins, $bin;
898       $downloadsizek += $bv->{'sizek'};
899     } else {
900       $ret{$bin} = {'name' => $bv->{'name'}, 'hdrmd5' => $bv->{'hdrmd5'}};
901       if (!$nometa && $havemeta) {
902         $ret{$bin}->{'meta'} = 1;
903       }
904     }
905   }
906   #print "(cache: ".@cacheold." hits, ".@downloadbins." misses)";
907   if (@downloadbins) {
908     if ($cachedir && $downloadsizek * 1024 * 100 > $cachesize) {
909       # reserve space
910       manage_cache($cachesize - $downloadsizek * 1024);
911     }
912     my @args;
913     push @args, "project=$projid";
914     push @args, "repository=$repoid";
915     push @args, "arch=$arch";
916     push @args, "binaries=".join(',', @downloadbins);
917     my $res = BSRPC::rpc({
918       'uri' => "$server/getbinaries",
919       'directory' => $dir,
920       'timeout' => $gettimeout,
921       'receiver' => \&BSHTTP::cpio_receiver,
922     }, undef, @args);
923     die("Error\n") unless ref($res) eq 'ARRAY';
924     my %havemeta;
925     for my $r (@$res) {
926       if ($r->{'name'} =~ /^(.*)\.(?:rpm|deb)$/) {
927         my $n = $1;
928         my @s = stat("$dir/$r->{'name'}");
929         die unless @s;
930         my $id = Build::queryhdrmd5("$dir/$r->{'name'}");
931         $r->{'hdrmd5'} = $id;
932         my $cacheid =  Digest::MD5::md5_hex("$projid/$repoid/$arch/$id");
933         push @cachenew, [$cacheid, $s[7], "$dir/$r->{'name'}"];
934         $ret{$n} = $r;
935       } elsif ($r->{'name'} =~ /^(.*)\.meta$/) {
936         $havemeta{$1} = 1;
937       }
938     }
939     for (keys %havemeta) {
940       next unless $ret{$_};
941       $ret{$_}->{'meta'} = 1;
942     }
943   }
944   manage_cache($cachesize, \@cacheold, \@cachenew) if $cachedir;
945   if ($nometa) {
946     for (keys %ret) {
947       next unless $ret{$_}->{'meta'};
948       unlink("$dir/$_.meta");
949       delete $ret{$_}->{'meta'};
950     }
951   }
952   return \%ret;
953 }
954
955 sub getbinaries_kiwiproduct {
956   my ($buildinfo, $dir, $srcdir) = @_;
957
958   # we need the Build package for queryhdrmd5
959   if (!defined &Build::queryhdrmd5) {
960     unshift @INC, "$statedir/build";
961     require Build;
962     Build->import();
963   }
964
965   # create list of prpaps
966   my @kdeps;
967   my %prpaps;
968   my %linkit;
969   for my $dep (@{$buildinfo->{'bdep'} || []}) {
970     if (!defined($dep->{'package'})) {
971       push @kdeps, $dep->{'name'};
972       next;
973     }
974     my $repoarch = $dep->{'repoarch'} || $buildinfo->{'arch'};
975     next if $repoarch eq 'src';
976     $prpaps{"$dep->{'project'}/$dep->{'repository'}/$repoarch/$dep->{'package'}"} = 1;
977     $linkit{"$dep->{'project'}/$dep->{'repository'}/$repoarch/$dep->{'package'}/$dep->{'name'}"} = 1 unless $dep->{'noinstall'};
978   }
979
980   my %prp2server;
981   for (@{$buildinfo->{'path'} || []}) {
982     $prp2server{"$_->{'project'}/$_->{'repository'}"} = $_->{'server'};
983   }
984
985   mkdir_p($dir);
986
987   # fetch packages needed for product building
988   for my $repo (@{$buildinfo->{'syspath'} || $buildinfo->{'path'} || []}) {
989     last if !@kdeps;
990     my $repoarch = $buildinfo->{'arch'};
991     $repoarch = $BSConfig::localarch if $repoarch eq 'local' && $BSConfig::localarch;
992     my $server = $repo->{'server'} || $buildinfo->{'reposerver'};
993     my $got = getbinaries_cache($dir, $server, $repo->{'project'}, $repo->{'repository'}, $repoarch, 1, \@kdeps);
994     @kdeps = grep {!$got->{$_}} @kdeps;
995   }
996   die("getbinaries_kiwiproduct: missing packages: @kdeps\n") if @kdeps;
997
998   my %meta;
999   my $linklocal = $localkiwi && -d "$localkiwi/build" ? 1 : 0;
1000
1001   my %cachenew;
1002   my @cacheold;
1003   my $downloadsizek = 0;
1004
1005   for my $prpap (sort keys %prpaps) {
1006     my ($projid, $repoid, $arch, $packid) = split('/', $prpap, 4);
1007     my $prpdir = "$projid/$repoid";
1008     my $ddir = "$srcdir/repos/$prpdir";
1009     mkdir_p($ddir);
1010     my $res;
1011     my $server =  $prp2server{"$projid/$repoid"} || $buildinfo->{'reposerver'};
1012     my %knownmd5;
1013     if ($linklocal) {
1014       $res = [];
1015       for my $name (ls("$localkiwi/build/$projid/$repoid/$arch/$packid")) {
1016         next unless $name =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1017         my $rarch = $1;
1018         mkdir_p("$ddir/$rarch") unless -d "$ddir/$rarch";
1019         if (!link("$localkiwi/build/$projid/$repoid/$arch/$packid/$name", "$ddir/$rarch/$name")) {
1020           # link fails if file already exists, rpc just overwrites
1021           unlink("$ddir/$rarch/$name");
1022           link("$localkiwi/build/$projid/$repoid/$arch/$packid/$name", "$ddir/$rarch/$name") || die("link $localkiwi/build/$projid/$repoid/$arch/$packid/$name $ddir/$rarch/$name: $!\n");
1023         }
1024         push @$res, {'name' => "$rarch/$name"};
1025       }
1026     } else {
1027       my $bvl;
1028       if ($cachedir) {
1029         eval {
1030           $bvl = BSRPC::rpc({
1031             'uri' => "$server/build/$projid/$repoid/$arch/$packid",
1032             'timeout' => $gettimeout,
1033           }, $BSXML::binaryversionlist, 'view=binaryversions');
1034         };
1035         undef $bvl if $@;
1036         warn($@) if $@;
1037       }
1038       my @good;
1039       my @bad;
1040       if ($bvl) {
1041         for my $bv (@{$bvl->{'binary'} || []}) {
1042           my $bin = $bv->{'name'};
1043           next unless $bin =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1044           my $rarch = $1;
1045           my $cacheid =  Digest::MD5::md5_hex("$projid/$repoid/$arch/$bv->{'hdrmd5'}");
1046           my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
1047           mkdir_p("$ddir/$rarch");
1048           if (link_or_copy($cachefile, "$ddir/$rarch/$bin.new.rpm")) {
1049             my @s = stat("$ddir/$rarch/$bin.new.rpm");
1050             die unless @s;
1051             my $leadsigmd5 = '';
1052             my $id = Build::queryhdrmd5("$ddir/$rarch/$bin.new.rpm", \$leadsigmd5);
1053             if ($id eq $bv->{'hdrmd5'} && (!$bv->{'leadsigmd5'} || $bv->{'leadsigmd5'} eq $leadsigmd5)) {
1054               push @good, "$rarch/$bin";
1055               push @cacheold, [$cacheid, $s[7]];
1056               $knownmd5{"$rarch/$bin"} = $id;
1057             } else {
1058               unlink "$ddir/$rarch/$bin.new.rpm";
1059               push @bad, $bv;
1060               $downloadsizek += $bv->{'sizek'};
1061             }
1062           } else {
1063             push @bad, $bv;
1064             $downloadsizek += $bv->{'sizek'};
1065           }
1066         }
1067       }
1068       #print "(cache: ".@good." hits, ".@bad." misses)";
1069       if ($bvl && @bad) {
1070         if ($cachedir && $downloadsizek * 1024 * 100 > $cachesize) {
1071           # reserve space
1072           manage_cache($cachesize - $downloadsizek * 1024, \@cacheold, [ values %cachenew ]);
1073           @cacheold = ();
1074           %cachenew = ();
1075           $downloadsizek = 0;
1076         }
1077         eval {
1078           BSRPC::rpc({
1079             'uri' => "$server/build/$projid/$repoid/$arch/$packid",
1080             'directory' => $ddir,
1081             'timeout' => $gettimeout,
1082             'map' => sub {
1083               my ($param, $name) = @_;
1084               if ($name =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/) {
1085                 my $rarch = $1;
1086                 mkdir_p("$param->{'directory'}/$rarch");
1087                 $name = "$rarch/$name.new.rpm";
1088               }
1089               return $name;
1090             },
1091             'receiver' => \&BSHTTP::cpio_receiver,
1092           }, undef, 'view=cpio', map {"binary=$_->{'name'}"} @bad);
1093         };
1094         if ($@) {
1095           # delete everything as a file might be incomplete
1096           for my $bv (@bad) {
1097             my $bin = $bv->{'name'};
1098             next unless $bin =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1099             my $rarch = $1;
1100             unlink("$ddir/$rarch/$bin.new.rpm");
1101           }
1102         }
1103         for my $bv (splice @bad) {
1104           my $bin = $bv->{'name'};
1105           next unless $bin =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1106           my $rarch = $1;
1107           my @s = stat("$ddir/$rarch/$bin.new.rpm");
1108           if (!@s) {
1109             push @bad, $bv;
1110             next;
1111           }
1112           my $leadsigmd5 = '';
1113           my $id = Build::queryhdrmd5("$ddir/$rarch/$bin.new.rpm", \$leadsigmd5);
1114           if ($id eq $bv->{'hdrmd5'} && (!$bv->{'leadsigmd5'} || $bv->{'leadsigmd5'} eq $leadsigmd5)) {
1115             push @good, "$rarch/$bin";
1116             my $cacheid =  Digest::MD5::md5_hex("$projid/$repoid/$arch/$bv->{'hdrmd5'}");
1117             $cachenew{"$ddir/$rarch/$bin"} = [$cacheid, $s[7], "$ddir/$rarch/$bin"];
1118             $knownmd5{"$rarch/$bin"} = $id;
1119           } else {
1120             unlink "$ddir/$rarch/$bin.new.rpm";
1121             push @bad, $bv;
1122           }
1123         }
1124         #print "(still ".@bad." misses)";
1125       }
1126       if ($bvl && !@bad) {
1127         for (@good) {
1128           rename("$ddir/$_.new.rpm", "$ddir/$_") || die("rename $ddir/$_.new.rpm $ddir/$_: $!\n");
1129         }
1130         $res = [ map {{'name' => $_}} @good ];
1131       } else {
1132         %knownmd5 = ();
1133         for (@good) {
1134           unlink("$ddir/$_.new.rpm");
1135           delete $cachenew{"$ddir/$_"};
1136         }
1137         $res = BSRPC::rpc({
1138           'uri' => "$server/build/$projid/$repoid/$arch/$packid",
1139           'directory' => $ddir,
1140           'timeout' => $gettimeout,
1141           'map' => sub {
1142             my ($param, $name) = @_;
1143             if ($name =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/) {
1144               my $rarch = $1;
1145               mkdir_p("$param->{'directory'}/$rarch");
1146               $name = "$rarch/$name";
1147             }
1148             return $name;
1149           },
1150           'receiver' => \&BSHTTP::cpio_receiver,
1151         }, undef, 'view=cpio');
1152         if ($cachedir) {
1153           for my $f (@{$res || []}) {
1154             my @s = stat("$ddir/$f->{'name'}");
1155             next unless @s;
1156             my $id = Build::queryhdrmd5("$ddir/$f->{'name'}");
1157             next unless $id;
1158             my $cacheid =  Digest::MD5::md5_hex("$projid/$repoid/$arch/$id");
1159             $cachenew{"$ddir/$f->{'name'}"} = [$cacheid, $s[7], "$ddir/$f->{'name'}"];
1160             $knownmd5{$f->{'name'}} = $id;
1161           }
1162           #print "(put ".@cachenew." entries)";
1163         }
1164       }
1165     }
1166     die unless $res;
1167     my @todometa;
1168     my @todometa_f;
1169     for my $f (@$res) {
1170       my (undef, $name) = split('/', $f->{'name'}, 2);
1171       next unless defined($name) && $name =~ /^(.*)-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1172       my ($n, $rarch) = ($1, $2);
1173       my $id = $knownmd5{$f->{'name'}};
1174       $id ||= Build::queryhdrmd5("$ddir/$f->{'name'}");
1175       $id ||= 'deaddeaddeaddeaddeaddeaddeaddead';
1176       if ($linkit{"$prpap/$n"} && $rarch ne 'src' && $rarch ne 'nosrc') {
1177         link("$ddir/$f->{'name'}", "$dir/$n.rpm") unless -e "$dir/$n.rpm";
1178       }
1179       $meta{"$prpap/$n.$rarch"} = $id;
1180     }
1181   }
1182
1183   manage_cache($cachesize, \@cacheold, [ values %cachenew ]) if $cachedir;
1184
1185   # create meta
1186   my @meta;
1187   for (sort keys %meta) {
1188     push @meta, "$meta{$_}  $_";
1189   } 
1190   return @meta;
1191 }
1192
1193 sub getbinaries {
1194   my ($buildinfo, $dir, $srcdir) = @_;
1195
1196   mkdir_p($dir);
1197   my $kiwimode;
1198   $kiwimode = 1 if $buildinfo->{'file'} =~ /\.kiwi$/;
1199
1200   if ($kiwimode && $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product') {
1201     return getbinaries_kiwiproduct($buildinfo, $dir, $srcdir);
1202   }
1203
1204   # we need the Build package for queryhdrmd5
1205   if (!defined &Build::queryhdrmd5) {
1206     unshift @INC, "$statedir/build";
1207     require Build;
1208     Build->import();
1209   }
1210
1211   my @bdep = @{$buildinfo->{'bdep'} || []};
1212   my %bdep_noinstall = map {$_->{'name'} => 1} grep {$_->{'noinstall'} && ($_->{'repoarch'} || '') ne 'src'} @bdep;
1213   my %bdep_notmeta = map {$_->{'name'} => 1} grep {$_->{'notmeta'} && ($_->{'repoarch'} || '') ne 'src'} @bdep;
1214   @bdep = map {$_->{'name'}} grep {($_->{'repoarch'} || '') ne 'src'} @bdep;
1215
1216   my %done;
1217   my @todo = @bdep;
1218   die("no binaries needed for this package?\n") unless @todo;
1219   my @meta;
1220   my %meta;
1221   my $projid = $buildinfo->{'project'};
1222   my $repoid = $buildinfo->{'repository'};
1223
1224   if ($kiwimode && $buildinfo->{'syspath'}) {
1225     # two path mode: fetch environment
1226     @todo = grep {!$bdep_noinstall{$_}} @bdep;
1227     for my $repo (@{$buildinfo->{'syspath'} || []}) {
1228       last if !@todo;
1229       my $server = $repo->{'server'} || $buildinfo->{'reposerver'};
1230       my $got = getbinaries_cache($dir, $server, $repo->{'project'}, $repo->{'repository'}, $buildinfo->{'arch'}, 1, \@todo);
1231       @todo = grep {!$got->{$_}} @todo;
1232     }
1233     die("getbinaries: missing packages: @todo\n") if @todo;
1234     @todo = grep {$bdep_noinstall{$_} || !$bdep_notmeta{$_}} @bdep;
1235   }
1236
1237   for my $repo (@{$buildinfo->{'path'} || []}) {
1238     last if !@todo && !$kiwimode;
1239     my @args;
1240     my $ddir = $dir;
1241     my $dlbins;
1242     if ($kiwimode) {
1243       my $prpdir = "$repo->{'project'}/$repo->{'repository'}";
1244       $ddir = "$srcdir/repos/$prpdir";
1245       # Always try to get all binaries, because kiwi needs to decide which to take
1246       $dlbins = \@bdep;
1247     } else {
1248       $ddir = $dir;
1249       # get only missing packages
1250       $dlbins = \@todo;
1251     }
1252     my $nometa = 0;
1253     $nometa = 1 if $kiwimode || $repo->{'project'} ne $projid || $repo->{'repository'} ne $repoid;
1254     my $got = getbinaries_cache($ddir, $repo->{'server'}, $repo->{'project'}, $repo->{'repository'}, $buildinfo->{'arch'}, $nometa, $dlbins);
1255     for (keys %$got) {
1256       $done{$_} = $got->{$_}->{'name'};
1257       $meta{$_} = 1 if !$nometa && $got->{$_}->{'meta'};
1258     }
1259     @todo = grep {!$done{$_}} @todo;
1260     if ($kiwimode) {
1261       my @m;
1262       for my $n (keys %$got) {
1263         my $f = $got->{$n};
1264         if (!$bdep_notmeta{$n}) {
1265           my $id = Build::queryhdrmd5("$ddir/$f->{'name'}") || "deaddeaddeaddeaddeaddeaddeaddead";
1266           push @m, "$id  $repo->{'project'}/$repo->{'repository'}/$n";
1267         }
1268         if (!$buildinfo->{'syspath'} && !$bdep_noinstall{$n}) {
1269           if (!-e "$dir/$f->{'name'}") {
1270             link_or_copy("$ddir/$f->{'name'}", "$dir/$f->{'name'}") || die("link_or_copy $ddir/$f->{'name'} $dir/$f->{'name'}: $!\n");
1271           }
1272         }
1273       }
1274       push @meta, sort {substr($a, 34) cmp substr($b, 34)} @m;
1275     }
1276   }
1277   die("getbinaries: missing packages: @todo\n") if @todo;
1278
1279   if (!$kiwimode) {
1280     # generate meta data
1281     # we carefully prune entries here to keep the memory usage down
1282     # so that coolo's image builds work
1283     # - do not prune entries that have one of our subpacks in the
1284     #   path as they are handled in a special way
1285     # - use the same order as the code in BSBuild
1286     my %mseen;
1287     my @subp = @{$buildinfo->{'subpack'} || []};
1288     my $subpackre = '';
1289     for (@subp) {
1290       $subpackre .= "|/\Q$_\E/";
1291     }
1292     if ($subpackre) {
1293       $subpackre = substr($subpackre, 1);
1294       $subpackre = qr/$subpackre/;
1295     }
1296     for my $dep (sort {"$a/" cmp "$b/"} map {$_->{'name'}} grep {!$_->{'notmeta'}} @{$buildinfo->{'bdep'} || []}) {
1297       my $m;
1298       $m = readstr("$dir/$dep.meta", 1) if $meta{$dep};
1299       if (!$m) {
1300         my $id = Build::queryhdrmd5("$dir/$done{$dep}") || "deaddeaddeaddeaddeaddeaddeaddead";
1301         push @meta, "$id  $dep";
1302       } else {
1303         chomp $m;
1304         my @m = split("\n", $m);
1305         $m[0] =~ s/  .*/  $dep/;
1306         push @meta, shift @m;
1307         if ($subpackre && "/$dep/" =~ /$subpackre/) {
1308           s/  /  $dep\// for @m;
1309           push @meta, @m;
1310         } else {
1311           for (@m) {
1312             next if $mseen{$_};
1313             my $x = $_;
1314             s/  /  $dep\//;
1315             $mseen{$x} = 1 unless $subpackre && "$_/" =~ /$subpackre/;
1316             push @meta, $_;
1317           }
1318         }
1319       }
1320     }
1321     @meta = BSBuild::gen_meta("$buildinfo->{'srcmd5'}  $buildinfo->{'package'}", $buildinfo->{'subpack'} || [], @meta);
1322     shift @meta;        # strip srcinfo again
1323   }
1324   return @meta;
1325 }
1326
1327 sub getoldpackages {
1328   my ($buildinfo, $odir) = @_;
1329
1330   # get old package build for compare and diffing tools
1331   mkdir_p($odir) || die("mkdir_p $odir: $!\n");
1332   my $res = BSRPC::rpc({
1333     'uri' => "$buildinfo->{'reposerver'}/build/$buildinfo->{'project'}/$buildinfo->{'repository'}/$buildinfo->{'arch'}/$buildinfo->{'package'}",
1334     'directory' => $odir,
1335     'timeout' => $gettimeout,
1336     'receiver' => \&BSHTTP::cpio_receiver,
1337   }, undef, 'view=cpio', 'noajax=1');
1338   rmdir($odir); # if not needed
1339 }
1340
1341
1342 sub patchkiwi {
1343   my ($buildinfo, $kiwifile, $meta) = @_;
1344
1345   my $kiwi = readxml($kiwifile, $BSKiwiXML::kiwidesc);
1346   die("no instsource section in kiwi file\n") unless $kiwi->{'instsource'};
1347   my $repo_only;
1348   my @vars;
1349   my $tag_media;
1350   for my $productvar (@{$kiwi->{'instsource'}->{'productoptions'}->{'productvar'} || []}) {
1351     push @vars, $productvar;
1352     $repo_only = 1 if $productvar->{'name'} eq 'REPO_ONLY' and $productvar->{'_content'} eq 'true';
1353     if ($productvar->{'name'} eq 'MEDIUM_NAME') {
1354       my $mediumbase = $productvar->{'_content'};
1355       $mediumbase .= sprintf("-Build%04d", $buildinfo->{'bcnt'} || 0);
1356       pop @vars;
1357       push @vars, { 'name' => 'BUILD_ID', '_content' => $mediumbase };
1358       push @vars, { 'name' => 'MEDIUM_NAME', '_content' => "$mediumbase-Media" };
1359     } elsif ($productvar->{'name'} eq 'RUN_MEDIA_CHECK' && $productvar->{'_content'} eq 'true') {
1360       $tag_media = 1;
1361     }
1362   }
1363   $kiwi->{'instsource'}->{'productoptions'}->{'productvar'} = \@vars;
1364
1365   for my $repopackages (@{$kiwi->{'instsource'}->{'repopackages'} || []}) {
1366     next unless grep {$_->{'name'} eq '*'} @{$repopackages->{'repopackage'} || []};
1367     # hey, a substitute all modifier!
1368     my @rp;
1369     my %allpkgs;
1370     for my $m (@$meta) {
1371       # md5  proj/rep/arch/pack/bin.arch
1372       my @s = split('/', $m);
1373       next unless $s[-1] =~ /^(.*)\.([^\.]*)$/;
1374       next if $2 eq 'src' || $2 eq 'nosrc';
1375       $allpkgs{$1} ||= {};
1376       $allpkgs{$1}->{$2} = 1;
1377     }
1378     for my $rp (@{$repopackages->{'repopackage'} || []}) {
1379       if ($rp->{'name'} ne '*') {
1380         push @rp, $rp;
1381         next;
1382       }
1383
1384       for my $pkg (sort keys %allpkgs) {
1385         # exclude blind take of all debug packages. They will be taken
1386         # automatically if a configured debug medium exists.
1387         next if $pkg =~ /-debuginfo$/;
1388         next if $pkg =~ /-debugsource$/;
1389         next if $pkg =~ /-debuginfo-32bit$/;
1390         next if $pkg =~ /-debugsource-32bit$/;
1391         next if $pkg =~ /-debuginfo-64bit$/;
1392         next if $pkg =~ /-debugsource-64bit$/;
1393         next if $pkg =~ /-debuginfo-x86$/;
1394         next if $pkg =~ /-debugsource-x86$/;
1395         my @a;
1396         for my $availableArch (sort keys %{$allpkgs{$pkg}}){
1397           if ($availableArch eq "noarch") {
1398             for my $a (sort map { $_->{'ref'} } @{$kiwi->{'instsource'}->{'architectures'}->{'requiredarch'}}) {
1399               push @a, $a;
1400            }
1401           }else{
1402             push @a, $availableArch;
1403           }
1404         }
1405         push @rp, {'name' => $pkg, 'arch' => "".join(",", @a)};
1406       }
1407     }
1408     $repopackages->{'repopackage'} = \@rp;
1409   }
1410   writexml($kiwifile, undef, $kiwi, $BSKiwiXML::kiwidesc);
1411 }
1412
1413 sub dobuild {
1414   my ($buildinfo) = @_;
1415
1416   my $projid = $buildinfo->{'project'};
1417   my $packid = $buildinfo->{'package'};
1418   my $repoid = $buildinfo->{'repository'};
1419   my $arch = $buildinfo->{'arch'};
1420   my $kiwimode;
1421   $kiwimode = 1 if $buildinfo->{'file'} =~ /\.kiwi$/;
1422   my $deltamode;
1423   $deltamode = 1 if $buildinfo->{'file'} eq '_delta';
1424
1425   my $helper = '';
1426   /^\Q$arch\E:(.*)$/ && ($helper = $1) for @{$cando{$hostarch}};
1427
1428   my @lt = localtime(time());
1429   my $timestring = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $lt[5] + 1900, $lt[4] + 1, @lt[3,2,1,0];
1430   
1431   print "$timestring: building '$packid' for project '$projid' repository '$repoid' arch '$arch'";
1432   print " using helper $helper" if $helper;
1433   print "\n";
1434
1435   my $srcdir    = "$buildroot/.build-srcdir";
1436   my $pkgdir    = "$buildroot/.pkgs";
1437   my $oldpkgdir = "$buildroot/.build.oldpackages";
1438
1439   if ($vm_tmpfs_mode) {
1440     my @targs;
1441     push @targs, "mount", "-t", "tmpfs", "none", $buildroot;
1442     qsystem(@targs) && die("mount tmpfs failed: $!\n");
1443   }
1444
1445   unlink("$buildroot/.build.meta");
1446   rm_rf("$buildroot/.build.packages") if -d "$buildroot/.build.packages";
1447   rm_rf($srcdir) if -d $srcdir;
1448   # changed to cleandir so that pkgdir can be a symlink
1449   BSUtil::cleandir($pkgdir) if -d $pkgdir;
1450   rm_rf($oldpkgdir) if -d $oldpkgdir;
1451
1452   my @meta;
1453   print "fetching sources, ";
1454   mkdir($srcdir) || die("mkdir $srcdir: $!\n");
1455   if ($deltamode) {
1456     push @meta, getdeltasources($buildinfo, $srcdir);
1457     print "packages, ";
1458     getbinaries($buildinfo, $pkgdir, $srcdir);
1459     undef $oldpkgdir;
1460     my $spec = readstr("$statedir/worker/worker-deltagen.spec", 1) || readstr("worker-deltagen.spec");
1461     writestr("$srcdir/worker-deltagen.spec", undef, $spec);
1462     $buildinfo->{'file'} = 'worker-deltagen.spec';
1463   } else {
1464     push @meta, getsources($buildinfo, $srcdir);
1465     print "packages, ";
1466     push @meta, getbinaries($buildinfo, $pkgdir, $srcdir);
1467   }
1468
1469   writestr("$buildroot/.build.meta", undef, join("\n", @meta)."\n");
1470
1471   getoldpackages($buildinfo, $oldpkgdir) if $oldpkgdir && !$kiwimode;
1472
1473   my @configpath;
1474   if ($kiwimode) {
1475     @configpath = map {"path=$_->{'project'}/$_->{'repository'}"} @{$buildinfo->{'syspath'} || $buildinfo->{'path'} || []};
1476     unshift @configpath, "path=$projid/$repoid" unless @configpath;
1477   }
1478   my $server = $buildinfo->{'srcserver'} || $srcserver;
1479   my $config = BSRPC::rpc("$server/getconfig", undef, "project=$projid", "repository=$repoid", @configpath);
1480   writestr("$buildroot/.build.config", undef, $config);
1481
1482   my $release = $buildinfo->{'release'};
1483   my $obsinstance = $BSConfig::obsname || '';
1484   my $disturl = "obs://$obsinstance/$projid/$repoid/$buildinfo->{'srcmd5'}-$packid";
1485
1486   my @args;
1487   push @args, $helper if $helper;
1488
1489   # build rpmlist for build script
1490   my @rpmlist;
1491   my @bdep = @{$buildinfo->{'bdep'} || []};
1492   for my $bdep (@bdep) {
1493     next if $bdep->{'package'} || $bdep->{'noinstall'} || ($bdep->{'repoarch'} && $bdep->{'repoarch'} eq 'src');
1494     my $bin = $bdep->{'name'};
1495     if (-e "$pkgdir/$bin.rpm") {
1496       push @rpmlist, "$bin $pkgdir/$bin.rpm";
1497     } elsif (-e "$pkgdir/$bin.deb") {
1498       push @rpmlist, "$bin $pkgdir/$bin.deb";
1499     } else {
1500       die("missing package: $bin\n");
1501     }
1502   }
1503   push @rpmlist, "localkiwi $localkiwi/localkiwi.rpm" if $localkiwi && -e "$localkiwi/localkiwi.rpm";
1504   push @rpmlist, "preinstall: ".join(' ', map {$_->{'name'}} grep {$_->{'preinstall'}} @bdep);
1505   push @rpmlist, "vminstall: ".join(' ', map {$_->{'name'}} grep {$_->{'vminstall'}} @bdep);
1506   push @rpmlist, "runscripts: ".join(' ', map {$_->{'name'}} grep {$_->{'runscripts'}} @bdep);
1507   if (grep {$_->{'cbpreinstall'}} @bdep) {
1508     push @rpmlist, "cbpreinstall: ".join(' ', map {$_->{'name'}} grep {$_->{'cbpreinstall'}} @bdep);
1509   }
1510   if (grep {$_->{'cbinstall'}} @bdep) {
1511     push @rpmlist, "cbinstall: ".join(' ', map {$_->{'name'}} grep {$_->{'cbinstall'}} @bdep);
1512   }
1513   writestr("$buildroot/.build.rpmlist", undef, join("\n", @rpmlist)."\n");
1514
1515   print "building...\n";
1516
1517   if ($kiwimode && $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product') {
1518     patchkiwi($buildinfo, "$srcdir/$buildinfo->{'file'}", \@meta);
1519   }
1520
1521   push @args, "$statedir/build/build";
1522   if ($vm =~ /(xen|kvm)/) {
1523     # add a bit delay - not all workers should start at the same time after idle.
1524     sleep (int(rand(5)) + 1); 
1525     mkdir("$buildroot/.mount") unless -d "$buildroot/.mount";
1526     push @args, '--root', "$buildroot/.mount";
1527     push @args, $vm, "$vm_root";
1528     push @args, '--swap', "$vm_swap";
1529     my $vmmemory = readstr("$buildroot/memory", 1);
1530     $vmmemory = $vm_memory if $vm_memory;
1531     push @args, '--memory', $vmmemory if $vmmemory;
1532     push @args, '--vm-kernel', $vm_kernel if $vm_kernel;
1533     push @args, '--vm-initrd', $vm_initrd if $vm_initrd;
1534     push @args, '--vmdisk-rootsize', $vmdisk_rootsize if $vmdisk_rootsize;
1535     push @args, '--vmdisk-swapsize', $vmdisk_swapsize if $vmdisk_swapsize;
1536     push @args, '--vmdisk-filesystem', $vmdisk_filesystem if $vmdisk_filesystem;
1537   } elsif ($vm =~ /lxc/) {
1538     push @args, '--root', $buildroot;
1539     push @args, '--vm-type', $vm;
1540   } else {
1541     push @args, '--root', $buildroot;
1542   }
1543   push @args, '--clean';
1544   push @args, '--changelog';
1545   push @args, '--oldpackages', $oldpkgdir if $oldpkgdir && -d $oldpkgdir;
1546   push @args, '--norootforbuild' unless $BSConfig::norootexceptions && grep {"$projid/$packid" =~ /^$_$/} keys %$BSConfig::norootexceptions;
1547   push @args, '--baselibs-internal';
1548   push @args, '--lint';
1549   push @args, '--dist', "$buildroot/.build.config";
1550   push @args, '--rpmlist', "$buildroot/.build.rpmlist";
1551   push @args, '--logfile', "$buildroot/.build.log";
1552   push @args, '--release', "$release" if defined $release;
1553   push @args, '--debug' if $buildinfo->{'debuginfo'};
1554   push @args, '--arch', $arch;
1555   push @args, '--jobs', $jobs if $jobs;
1556   push @args, '--reason', "Building $packid for project '$projid' repository '$repoid' arch '$arch' srcmd5 '$buildinfo->{'srcmd5'}'";
1557   push @args, '--disturl', $disturl;
1558   push @args, '--linksources' if $localkiwi;
1559   push @args, '--signdummy' if $kiwimode && !$localkiwi && $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product' && -e "$statedir/build/signdummy";
1560   push @args, "$srcdir/$buildinfo->{'file'}";
1561   qsystem(@args);
1562
1563   print "\n";
1564   print "$timestring: build finished '$packid' for project '$projid' repository '$repoid' arch '$arch'";
1565   print " using helper $helper" if $helper;
1566   print "\n";
1567
1568   my $ret = $?;
1569   if ($ret == 512) { # system exit code 2 maps to 2 * 256
1570     if (($buildinfo->{'reason'} || '') eq "rebuild counter sync") {
1571       $ret = 0;
1572     } else {
1573       return 2;
1574     }
1575   } elsif ($ret == 768) {
1576     return 3;
1577   }
1578   if ($ret) {
1579     return 1;
1580   }
1581   if (! -s "$buildroot/.build.log") {
1582     print "build succeeded, but no logfile?\n";
1583     return 1;
1584   }
1585
1586   if ($vm =~ /(xen|kvm)/) {
1587     rm_rf("$buildroot/.build.packages");
1588     if (! -d "$buildroot/.mount/.build.packages") {
1589       # old style, call extractbuild
1590       print "extracting built packages...\n";
1591       @args = ();
1592       push @args, "$statedir/build/extractbuild";
1593       push @args, '--root', "$vm_root";
1594       push @args, '--swap', "$vm_swap";
1595       if (system(@args)) {
1596         die("extractbuild failed\n");
1597       }
1598       mkdir_p("$buildroot/.build.packages");
1599       if (system("cd $buildroot/.build.packages && cpio --extract --no-absolute-filenames -v < $vm_swap")) {
1600         die("cpio extract failed\n");
1601       }
1602     } else {
1603       # new style, build already did extractbuild for us
1604       if(!rename("$buildroot/.mount/.build.packages", "$buildroot/.build.packages")) {
1605         print "final rename failed: $!";
1606         return 1;
1607       }
1608     }
1609     # XXX: extracted cpio is flat but code below expects those directories...
1610     symlink('.', "$buildroot/.build.packages/SRPMS");
1611     symlink('.', "$buildroot/.build.packages/DEBS");
1612     symlink('.', "$buildroot/.build.packages/KIWI");
1613   }
1614
1615   return 0;
1616 }
1617
1618
1619 if (defined($oneshot)) {
1620   $oneshot = time() + $oneshot;
1621 }
1622
1623 # better safe than sorry...
1624 chdir($statedir) || die("$statedir: $!\n");
1625
1626
1627 BSServer::deamonize(@ARGV) unless $oneshot;
1628 $SIG{'PIPE'} = 'IGNORE';
1629
1630 # calculate code meta md5
1631 my $workercode = codemd5('worker');
1632 my $buildcode = codemd5('build');
1633 $| = 1;
1634 print "starting worker $workercode build $buildcode\n";
1635
1636 open(RUNLOCK, '>>', "$statedir/lock") || die("$statedir/lock: $!");
1637 flock(RUNLOCK, LOCK_EX | LOCK_NB) || die("worker is already running on $statedir!\n");
1638 utime undef, undef, "$statedir/lock";
1639
1640 # we always start idle
1641 lockstate();
1642 unlink("$statedir/job");
1643 unlink("$buildroot/.build.log");
1644 commitstate({'state' => 'idle'});
1645
1646 # start server process...
1647 if ($port) {
1648   BSServer::serveropen($port);
1649 } else {
1650   BSServer::serveropen(\$port);
1651 }
1652 mkdir($buildroot) unless -d $buildroot;
1653 send_state('idle', $port, $hostarch);
1654
1655 my $idlecnt = 0;
1656 my $rekillcnt = 0;
1657
1658 my $conf = {
1659   'timeout' => 10,
1660 };
1661 while (!BSServer::server($conf)) {
1662   # timeout handler, called every 10 seconds
1663   my $state = readxml("$statedir/state", $BSXML::workerstate, 1);
1664   next unless $state;
1665
1666   if ($state->{'state'} eq 'idle') {
1667     if ($oneshot && time() > $oneshot) {
1668       send_state('exit', $port, $hostarch);
1669       print "exiting.\n";
1670       exit(0);
1671     }
1672     $idlecnt++;
1673     if ($idlecnt % 30 == 0) {
1674       # send idle message every 5 minutes in case the server was down
1675       $idlecnt = 0;
1676       send_state('idle', $port, $hostarch) if $state->{'state'} eq 'idle';
1677     }
1678   } else {
1679     $idlecnt = 0;
1680   }
1681
1682   if ($state->{'state'} eq 'exit') {
1683     $state = lockstate();
1684     if ($state->{'state'} eq 'exit') {
1685       $state = {'state' => 'idle'};
1686       commitstate($state);
1687       send_state('exit', $port, $hostarch);
1688       exit(0);
1689     }
1690     unlockstate();
1691   }
1692
1693   if ($state->{'state'} eq 'rebooting') {
1694     chdir("$statedir/worker") || die("$statedir/worker: $!");
1695     close RUNLOCK;
1696     exec("./bs_worker", @saveargv);
1697     die("$statedir/worker/bs_worker: $!\n");    # oops
1698   }
1699
1700   if ($state->{'state'} eq 'killed' || $state->{'state'} eq 'discarded') {
1701     $rekillcnt++;
1702     if ($state->{'state'} eq 'discarded' && $state->{'nextstate'}) {
1703       # signal early that we're going down
1704       send_state('exit', $port, $hostarch);
1705       $rekillcnt = 12;
1706     }
1707     if ($rekillcnt % 12 == 0) {
1708       # re-kill after 2 minutes, maybe build is stuck somewhere
1709       $rekillcnt = 0;
1710       $state = lockstate();
1711       if ($state->{'state'} eq 'killed' || $state->{'state'} eq 'discarded') {
1712         kill_job();
1713       }
1714       unlockstate();
1715     }
1716   } else {
1717     $rekillcnt = 0;
1718   }
1719
1720   next unless $state->{'state'} eq 'building';
1721
1722   my $locked = -1;
1723   while ($locked++ < 1) {
1724     $state = lockstate() if $locked == 1;
1725     last if $state->{'state'} ne 'building';
1726     my $ct = time();
1727     my @s = stat("$buildroot/.build.log");
1728     next unless @s;
1729     if ($s[7] > $buildlog_maxsize) {
1730       next unless $locked;
1731       if (!kill_job()) {
1732         warn("could not kill job\n");
1733         last;
1734       }
1735       trunc_logfile("$buildroot/.build.log");
1736       $state->{'state'} = 'killed';
1737       commitstate($state);
1738       $locked = 0;
1739     } elsif ($ct - $s[9] > $buildlog_maxidle) {
1740       next unless $locked;
1741       if (!kill_job()) {
1742         warn("could not kill job\n");
1743         last;
1744       }
1745       local *F;
1746       if (open(F, '>>', "$buildroot/.build.log")) {
1747         print F "\n\nJob seems to be stuck here, killed.\n";
1748         close F;
1749       }
1750       $state->{'state'} = 'killed';
1751       commitstate($state);
1752       $locked = 0;
1753     }
1754     last;
1755   }
1756   unlockstate() if $locked;
1757 }
1758 close RUNLOCK;
1759
1760 my $req = BSServer::readrequest();
1761 my $path = $req->{'path'};
1762 my $cgi = BSServer::parse_cgi($req);
1763 if ($path eq '/info') {
1764   # check state?
1765   my $state = readxml("$statedir/state", $BSXML::workerstate, 1);
1766   if ($cgi->{'jobid'}) {
1767     die("not building a job\n") if $state->{'state'} ne 'building';
1768     die("building a different job\n") if $cgi->{'jobid'} ne $state->{'jobid'};
1769   }
1770   my $info;
1771   if ($state->{'state'} eq 'building') {
1772     $info = readstr("$statedir/job");
1773   } else {
1774     $info = "<buildinfo>\n  <error>".$state->{'state'}."</error>\n</buildinfo>\n";
1775   }
1776   BSServer::reply($info, 'Content-Type: text/xml');
1777   exit(0);
1778 } elsif ($path eq '/logfile') {
1779   my $state = readxml("$statedir/state", $BSXML::workerstate, 1);
1780   die("not building\n") if $state->{'state'} ne 'building';
1781   die("building a different job\n") if $cgi->{'jobid'} && $cgi->{'jobid'} ne $state->{'jobid'};
1782   if ($cgi->{'view'} && $cgi->{'view'} eq 'entry') {
1783     my @s = stat("$buildroot/.build.log");
1784     die("$buildroot/.build.log: $!\n") unless @s;
1785     my $xml = "<directory>\n  <entry name=\"_log\" size=\"$s[7]\" mtime=\"$s[9]\" />\n</directory>\n";
1786     BSServer::reply($xml, 'Content-Type: text/xml');
1787     exit(0);
1788   }
1789   stream_logfile($cgi->{'nostream'}, $cgi->{'start'}, $cgi->{'end'});
1790   exit(0);
1791 } elsif ($path eq '/kill' || $path eq '/discard') {
1792   my $state = lockstate();
1793   die("not building\n") if $state->{'state'} ne 'building';
1794   die("building a different job\n") if $cgi->{'jobid'} && $cgi->{'jobid'} ne $state->{'jobid'};
1795   if (!kill_job()) {
1796     die("could not kill job\n");
1797   }
1798   local *F;
1799   if (open(F, '>>', "$buildroot/.build.log")) {
1800     if ($path eq '/kill') {
1801       print F "\n\nKilled Job\n";
1802     } else {
1803       print F "\n\nDiscarded Job\n";
1804     }
1805     close F;
1806   }
1807   if ($path eq '/kill') {
1808     $state->{'state'} = 'killed';
1809     commitstate($state);
1810     BSServer::reply("<status=\"ok\" />\n", 'Content-Type: text/xml');
1811   } else {
1812     $state->{'state'} = 'discarded';
1813     commitstate($state);
1814     BSServer::reply("<status=\"ok\" />\n", 'Content-Type: text/xml');
1815   }
1816   exit(0);
1817 } elsif ($path ne '/build' || $req->{'action'} ne 'PUT') {
1818   die("unknown request: $path\n");
1819 }
1820
1821 # Check for XEN daemon database leak
1822 if ($vm eq "--xen" && $xenstore_maxsize && 0 + (-s '/var/lib/xenstored/tdb') > $xenstore_maxsize) {
1823   die("xenstore too big:".(-s '/var/lib/xenstored/tdb')."\n");
1824 }
1825
1826 my $state = lockstate();
1827 if ($cgi->{'workercode'} && $cgi->{'port'} && $cgi->{'workercode'} ne $workercode && !$noworkercheck) {
1828   $state->{'state'} = 'rebooting';
1829   my $peer = "${BSServer::peer}:$cgi->{'port'}";
1830   $workercode = getcode('worker', "http://$peer/getworkercode");
1831   if (!$workercode) {
1832     $state->{'state'} = 'broken';       # eek
1833   } else {
1834     print "activating new worker code $workercode\n";
1835   }
1836   commitstate($state);
1837   die("rebooting...\n");
1838 }
1839
1840 die("I am not idle!\n") unless $state->{'state'} eq 'idle';
1841
1842 BSServer::read_file('job.new');
1843 my $infoxml = readstr('job.new');
1844 die("bad job xml data\n") unless $infoxml =~ /<.*?>/s;
1845 my $buildinfo = XMLin($BSXML::buildinfo, $infoxml);
1846 my $jobid = $cgi->{'jobid'};
1847 $jobid ||= Digest::MD5::md5_hex($infoxml);
1848
1849 $buildinfo->{'jobid'} = $jobid;
1850
1851 # old buildinfos missed some entries
1852 if (@{$buildinfo->{'path'} || []}) {
1853   $buildinfo->{'project'} ||= $buildinfo->{'path'}->[0]->{'project'};
1854   $buildinfo->{'repository'} ||= $buildinfo->{'path'}->[0]->{'repository'};
1855   $buildinfo->{'reposerver'} ||= $buildinfo->{'path'}->[0]->{'server'};
1856 }
1857
1858 if ($localkiwi) {
1859   # make sure this is the right job for us
1860   die("not a kiwi job\n") unless $buildinfo->{'file'} =~ /\.kiwi$/;
1861   die("not a kiwi product job\n") unless $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product';
1862 }
1863
1864 $buildcode = codemd5('build');
1865 if (!$nobuildcodecheck && $cgi->{'buildcode'} && $cgi->{'port'} && $cgi->{'buildcode'} ne $buildcode) {
1866   print "fetching new buildcode $cgi->{'buildcode'}, mine was $buildcode\n";
1867   my $peer = "${BSServer::peer}:$cgi->{'port'}";
1868   $buildcode = getcode('build', "http://$peer/getbuildcode");
1869   die("could not update build code\n") unless $buildcode;
1870 }
1871
1872 rename('job.new', 'job') || die("rename job.new job: $!\n");
1873
1874 if ($hostcheck) {
1875   my $server = $buildinfo->{'srcserver'} || $srcserver;
1876   if (system($hostcheck, '--srcserver', $server, "$statedir/job", 'precheck', $buildroot)) {
1877     unlink('job');
1878     die("400 cannot build this package\n");
1879   }
1880 }
1881
1882 if ($testmode) {
1883   BSServer::reply("<status code=\"failed\">\n  <details>testmode activated</details>\n</status>\n", 'Status: 400 Testmode', 'Content-Type: text/xml');
1884 } else {
1885   BSServer::reply("<status code=\"ok\">\n  <details>so much work, so little time...</details>\n</status>\n", 'Content-Type: text/xml');
1886 }
1887 print "got job, run build...\n";
1888 delete $SIG{'__DIE__'};
1889 unlink("$buildroot/.build.meta");
1890 unlink("$buildroot/.build.packages");
1891 unlink("$buildroot/.build.log");
1892 writestr("$buildroot/.build.log", undef, '');
1893
1894 $state->{'state'} = 'building';
1895 $state->{'jobid'} = $jobid;
1896 commitstate($state);
1897
1898 send_state('building', $port, $hostarch, $buildinfo->{'reposerver'});
1899
1900 my $ex;
1901 eval {
1902   $ex = dobuild($buildinfo);
1903 };
1904 if ($@) {
1905   local *F;
1906   if (open(F, '>>', "$buildroot/.build.log")) {
1907     print F $@;
1908     close(F);
1909   }
1910   print "$@";
1911   $ex = 1;
1912 }
1913
1914 # build is done, send back result
1915 $state = lockstate();
1916
1917 if ($state->{'state'} eq 'discarded') {
1918   # our poor job is no longer needed
1919   print "build discarded...\n";
1920   unlink("$buildroot/.build.log");
1921   unlink("$buildroot/job");
1922   cleanup_job();
1923
1924   if ($state->{'nextstate'}) {
1925     $state = {'state' => $state->{'nextstate'}};
1926     commitstate($state);
1927     exit(0);
1928   }
1929   $state = {'state' => 'idle'};
1930   commitstate($state);
1931   exit(0) if $oneshot && time() > $oneshot;
1932   send_state('idle', $port, $hostarch);
1933   exit(0);
1934 }
1935
1936 if ($state->{'state'} ne 'building') {
1937   # something is wrong, consider job bad
1938   $ex = 1;
1939 }
1940
1941 if (! -s "$buildroot/.build.log") {
1942   eval {
1943     if (defined($workerid)) {
1944       writestr("$buildroot/.build.log", undef, "build on $workerid did not create a logfile\n");
1945     } else {
1946       writestr("$buildroot/.build.log", undef, "build did not create a logfile\n");
1947     }
1948   };
1949   $ex = 3;
1950 }
1951
1952 if ($hostcheck) {
1953   print "running post-build host check\n";
1954   my $server = $buildinfo->{'srcserver'} || $srcserver;
1955   if (system($hostcheck, '--srcserver', $server, "$statedir/job", $ex ? 'failed' : 'succeeded', "$buildroot/.build.log")) {
1956     print "post-build host check failed\n";
1957     $ex = 3;
1958   }
1959 }
1960
1961 my @send;
1962 if ($ex == 0 && $buildinfo->{'reason'} ne "rebuild counter sync" && -f "$buildroot/.build.packages/same_result_marker") {
1963   $ex = 2;
1964 }
1965 if ($ex == 0) {
1966   my @d;
1967   push @d, map {"RPMS/$_"} sort(ls("$buildroot/.build.packages/RPMS"));
1968   push @d, 'SRPMS';
1969   @d = ('DEBS') if $buildinfo->{'file'} =~ /\.dsc$/;
1970   @d = ('KIWI') if $buildinfo->{'file'} =~ /\.kiwi$/;
1971   push @d, 'OTHER';
1972   for my $d ('.', @d) {
1973     my @files = sort(ls("$buildroot/.build.packages/$d"));
1974     @files = grep {$_ ne 'same_result_marker'} @files;
1975     @files = grep {-f "$buildroot/.build.packages/$d/$_"} @files;
1976     push @send, map {"$buildroot/.build.packages/$d/$_"} @files;
1977   }
1978   @send = map {{name => (split('/', $_))[-1], filename => $_}} @send;
1979   if (!@send && !$localkiwi) {
1980     print "build did not create anything to send back!\n";
1981     $ex = 1;
1982   }
1983 }
1984 my $code;
1985 if (!$ex) {
1986   print "build succeeded, send everything back...\n";
1987   $code = 'succeeded';
1988 } elsif ($ex == 2) {
1989   print "build succeeded, but does not differ from old build result...\n";
1990   $code = 'unchanged';
1991 } elsif ($ex == 3) {
1992   print "build failed, marked as bad build host...\n";
1993   $code = 'badhost';
1994   # try to be clever and avoid endless builds of people who broken their build enviroment
1995   if (open(LL, '<', "$buildroot/.build.log")) {
1996     my $data = '';
1997     sysseek(LL, -10240, 2);
1998     sysread(LL, $data, 10240);
1999     close LL;
2000     if ($data =~ /^mount: error while loading shared libraries:/) {
2001       $code = 'failed';
2002     }
2003   }
2004 } else {
2005   print "build failed, send back logfile...\n";
2006   $code = 'failed';
2007 }
2008 push @send, {name => 'meta', filename => "$buildroot/.build.meta"} if -e "$buildroot/.build.meta";
2009 push @send, {name => 'logfile', filename => "$buildroot/.build.log"};
2010
2011 if (!$testmode) {
2012   if ($localkiwi && -d "$localkiwi/jobs/$buildinfo->{'arch'}") {
2013     # local delivery hack...
2014     my @s = stat(_);
2015     my ($localkiwi_uid, $localkiwi_gid) = ($s[4], $s[5]);
2016     my $jobdir = "$localkiwi/jobs/$buildinfo->{'arch'}/$buildinfo->{'job'}:dir";
2017     mkdir_p($jobdir);
2018     chown($localkiwi_uid, $localkiwi_gid, $jobdir);
2019     BSUtil::cleandir($jobdir);
2020     for my $f (ls("$buildroot/.build.packages/KIWI")) {
2021       system('chown', '-h', '-R', '--reference', "$jobdir", "$buildroot/.build.packages/KIWI/$f");
2022       rename("$buildroot/.build.packages/KIWI/$f", "$jobdir/$f") || die("rename $buildroot/.build.packages/KIWI/$f $jobdir/$f: $!\n");
2023     }
2024     chown($localkiwi_uid, $localkiwi_gid, "$buildroot/.build.log");
2025     chown($localkiwi_uid, $localkiwi_gid, "$buildroot/.build.meta");
2026     rename("$buildroot/.build.log", "$jobdir/logfile");
2027     rename("$buildroot/.build.meta", "$jobdir/meta");
2028     @send = ();
2029   }
2030   my $param = {
2031     uri => "$buildinfo->{'reposerver'}/putjob",
2032     request => 'POST',
2033     headers => [ 'Content-Type: application/x-cpio' ],
2034     chunked => 1,
2035     data => \&BSHTTP::cpio_sender,
2036     cpiofiles => \@send,
2037   };
2038   my $now = time();
2039   my @args = ("job=$buildinfo->{'job'}", "arch=$buildinfo->{'arch'}", "jobid=$jobid", "code=$code", "now=$now");
2040   if ($code eq 'badhost') {
2041     # don't transmit anything in the badhost case
2042     $param = {
2043       uri => "$buildinfo->{'reposerver'}/putjob",
2044       request => 'POST',
2045     };
2046   }
2047   eval {
2048     my $res = BSRPC::rpc($param, undef, @args);
2049   };
2050   if ($@) {
2051     print "rpc failed: $@\nsleeping one minute just in case...\n";
2052     sleep(60);
2053   } else {
2054     print "sent, all done...\n";
2055   }
2056 } else {
2057   print "testmode, not sending anything\n";
2058   print Dumper(\@send);
2059 }
2060
2061 unlink("$buildroot/.build.log");
2062 unlink("$buildroot/job");
2063 print "\n";
2064 cleanup_job();
2065
2066 if ($state->{'nextstate'}) {
2067   $state = {'state' => $state->{'nextstate'}};
2068   commitstate($state);
2069   exit(0);
2070 }
2071 $state = {'state' => 'idle'};
2072 commitstate($state);
2073
2074 exit(0) if $oneshot && time() > $oneshot;
2075 send_state('idle', $port, $hostarch);
2076
2077 exit(0);