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