diff options
-rw-r--r-- | lib/Iurt/.perl_checker | 1 | ||||
-rw-r--r-- | lib/Iurt/Chroot.pm | 515 | ||||
-rw-r--r-- | lib/Iurt/Config.pm | 304 | ||||
-rw-r--r-- | lib/Iurt/DKMS.pm | 318 | ||||
-rw-r--r-- | lib/Iurt/File.pm | 68 | ||||
-rw-r--r-- | lib/Iurt/Mail.pm | 27 | ||||
-rw-r--r-- | lib/Iurt/Process.pm | 355 | ||||
-rw-r--r-- | lib/Iurt/Urpmi.pm | 789 | ||||
-rw-r--r-- | lib/Iurt/Util.pm | 203 | ||||
-rwxr-xr-x | uiurt | 1497 | ||||
-rwxr-xr-x | ulri | 786 |
11 files changed, 4863 insertions, 0 deletions
diff --git a/lib/Iurt/.perl_checker b/lib/Iurt/.perl_checker new file mode 100644 index 0000000..2326e2f --- /dev/null +++ b/lib/Iurt/.perl_checker @@ -0,0 +1 @@ +Basedir ../.. diff --git a/lib/Iurt/Chroot.pm b/lib/Iurt/Chroot.pm new file mode 100644 index 0000000..5f055fa --- /dev/null +++ b/lib/Iurt/Chroot.pm @@ -0,0 +1,515 @@ +package Iurt::Chroot; + +use strict; +use base qw(Exporter); +use MDV::Distribconf::Build; +use MDK::Common; +use Iurt::Process qw(clean sudo); +use Iurt::Config qw(dump_cache_par); +use Iurt::Util qw(plog); +use File::Temp 'mktemp'; +use File::Path 'mkpath'; + +our @EXPORT = qw( + clean_chroot_tmp + clean_unionfs + clean_all_chroot_tmp + clean_all_unionfs + clean_chroot + dump_rpmmacros + add_local_user + create_temp_chroot + remove_chroot + create_chroot + create_buid_chroot + check_chroot + check_build_chroot +); + +my $sudo = '/usr/bin/sudo'; + +=head2 clean_chroot($chroot, $run, $only_clean) + +Create or clean a chroot +I<$chroot> chroot path +I<$run> is the running environment +I<%only_clean> only clean the chroot, do not create a new one +Return true. + +=cut + +sub clean_chroot { + my ($chroot, $chroot_tar, $run, $config, $o_only_clean, $o_only_tar) = @_; + + plog('DEBUG', "clean chroot"); + if (-d $chroot && !$o_only_tar) { + system("$sudo umount $chroot/proc &> /dev/null"); + system("$sudo umount $chroot/dev/pts &> /dev/null"); + sudo($run, $config, '--rm', '-r', $chroot); + } + + return 1 if $o_only_clean; + + mkdir $chroot; + + # various integrity checking + return 1 if $o_only_tar && -f "$chroot/home/builder/.rpmmacros" && -d "$chroot/home/builder" && -d "$chroot/proc"; + + chdir $chroot; + + system($sudo, 'tar', 'xf', $chroot_tar) and create_build_chroot($chroot, $chroot_tar, $run, $config); + + create_build_chroot($chroot, $chroot_tar, $run, $config) if !-d "$chroot/proc" || !-d "$chroot/home/builder"; + + dump_rpmmacros($run, $config, "$chroot/home/builder/.rpmmacros") or return; + system("$sudo mount none -t proc $chroot/proc &>/dev/null") and return; + system("$sudo mount none -t devpts $chroot/dev/pts &>/dev/null") and return; + 1; +} + +sub dump_rpmmacros { + my ($run, $config, $file) = @_; + my $f; + + #plog("adding rpmmacros to $file"); + + if (!open $f, qq(| $sudo sh -c "cat > $file")) { + plog("ERROR: could not open $file ($!)"); + return 0; + } + my $packager = $run->{packager} || $config->{packager}; + + print $f qq(\%_topdir \%(echo \$HOME)/rpm +\%_tmppath \%(echo \$HOME)/rpm/tmp/ +\%distribution $config->{distribution} +\%vendor $config->{vendor} +\%packager $packager); + # need to be root for permission +} + +sub add_local_user { + my ($chroot_tmp, $run, $config, $luser, $uid) = @_; + my $program_name = $run->{program_name}; + + # change the builder user to the local user id + # FIXME it seems that unionfs does not handle well the change of the + # uid of files + # if (system(qq|sudo chroot $chroot_tmp usermod -u $run->{uid} builder|)) { + + # this should not be necessary as the builder user is supposed to have + # the macros already + + foreach my $p ('RPMS', 'BUILD', 'SPECS', 'SRPMS', 'SOURCES') { + -d "$chroot_tmp/home/builder/rpm/$p" and next; + sudo($run, $config, "--mkdir", "$chroot_tmp/home/builder/rpm/$p"); + } + + dump_rpmmacros($run, $config, "$chroot_tmp/home/builder/.rpmmacros") or return; + + if ($uid) { + if (system($sudo, 'chroot', $chroot_tmp, 'useradd', '-M', '-u', $uid, $luser) || + system("$sudo chroot $chroot_tmp id $luser >/dev/null 2>&1")) { + plog('ERR', "ERROR: setting userid $uid to $luser in " . + "$chroot_tmp failed, checking the chroot"); + check_build_chroot($run->{chroot_path}, $run->{chroot_tar}, $run, + $config) or return; + } + } else { + # the program has been launch as root, setting the home to /home/root for compatibility + system($sudo, 'chroot', $chroot_tmp, 'usermod', '-d', "/home/$luser", '-u', $uid, '-o', '-l', $luser, 'root'); + } + + if (system(qq($sudo chroot $chroot_tmp cp -R /home/builder /home/$luser))) { + plog("ERROR: could not initialized $luser directory"); + return; + } + + if (system(qq($sudo chroot $chroot_tmp chown -R $uid /home/$luser))) { + die "ERROR $program_name: could not initialized $luser directory\n"; + } + + 1; +} + +sub create_temp_chroot { + my ($run, $config, $cache, $union_id, $chroot_tmp, $chroot_tar, $srpm) = @_; + + my $home = $config->{local_home}; + my $debug_tag = $run->{debug_tag}; + my $unionfs_dir = $run->{unionfs_dir}; + + if ($run->{unionfs_tmp}) { + my $mount_point = "$unionfs_dir/unionfs.$run->{run}.$union_id"; + plog(2, "cleaning temp chroot $mount_point"); + if (!clean_mnt($run, $mount_point, $run->{verbose})) { + dump_cache_par($run); + die "FATAL: can't kill remaining processes acceding $mount_point"; + } + my $tmpfs; + + # we cannont just rm -rf $tmpfs, this create defunct processes + # afterwards (and lock particularly hard the urpmi database) + # + $union_id = clean_unionfs($unionfs_dir, $run, $run->{run}, $union_id); + $tmpfs = "$unionfs_dir/tmpfs.$run->{run}.$union_id"; + $chroot_tmp = "$unionfs_dir/unionfs.$run->{run}.$union_id"; + + if (!-d $tmpfs) { + if (!mkpath($tmpfs)) { + plog("ERROR: Could not create $tmpfs ($!)"); + return; + } + } + if (! -d $chroot_tmp) { + if (!mkpath($chroot_tmp)) { + plog("ERROR: Could not create $chroot_tmp ($!)"); + return; + } + } + if ($cache->{no_unionfs}{$srpm}) { + $run->{unionfs_tmp} = 0; + clean_chroot($chroot_tmp, $chroot_tar, $run, $config); + } else { + # if the previous package has been built without unionfs, chroot need to be cleaned + if (!$run->{unionfs_tmp}) { + clean_chroot($chroot_tmp, $chroot_tar, $run, $config); + } else { + # only detar the chroot if not already + clean_chroot($chroot_tmp, $chroot_tar, $run, $config, 0, 1); + } + $run->{unionfs_tmp} = 1; + if (system(qq($sudo mount -t tmpfs none $tmpfs &>/dev/null))) { + plog("ERROR: can't mount $tmpfs ($!)"); + return; + } + if (system(qq($sudo mount -o dirs=$tmpfs=rw:$home/chroot_$run->{distro_tag}$debug_tag=ro -t unionfs none $chroot_tmp &>/dev/null))) { + plog("ERROR: can't mount $tmpfs and $home/chroot_$run->{distro_tag}$debug_tag with unionfs ($!)"); + return; + } + if (system("$sudo mount -t proc none $chroot_tmp/proc &>/dev/null")) { + plog("ERROR: can't mount /proc in chroot $chroot_tmp ($!)"); + return; + } + if (!-d "$chroot_tmp/dev/pts") { + if (sudo($run, $config, "--mkdir", "$chroot_tmp/dev/pts")) { + plog("ERROR: can't create /dev/pts in chroot $chroot_tmp ($!)"); + return; + } + + if (system($sudo, "mount", "-t", "devpts", "none", "$chroot_tmp/dev/pts &>/dev/null")) { + plog("ERROR: can't mount /dev/pts in the chroot $chroot_tmp ($!)"); + return; + } + } + } + } else { + plog("Install new chroot"); + plog('DEBUG', "... in $chroot_tmp"); + clean_chroot($chroot_tmp, $chroot_tar, $run, $config); + } + $union_id, $chroot_tmp; +} + +sub remove_chroot { + my ($run, $dir, $func, $prefix) = @_; + + plog("Remove existing chroot"); + plog('DEBUG', "... dir $dir all $run->{clean_all} prefix $prefix"); + + if ($run->{clean_all}) { + opendir my $chroot_dir, $dir; + foreach (readdir $chroot_dir) { + next if !-d "$dir/$_" || /\.{1,2}/; + plog("cleaning old chroot for $_ in $dir"); + $func->($run, "$dir/$_", $prefix); + } + } else { + foreach my $user (@{$run->{clean}}) { + plog("cleaning old chroot for $user in $dir"); + $func->($run, "$dir/$user", $prefix); + } + } +} + +sub clean_mnt { + my ($run, $mount_point, $verbose) = @_; + return clean($run, $mount_point, "/sbin/fuser", "$sudo /sbin/fuser -k", $verbose); +} + +sub clean_all_chroot_tmp { + my ($run, $chroot_dir, $prefix) = @_; + + plog(1, "cleaning all old chroot remaining dir in $chroot_dir"); + + my $dir; + if (!opendir $dir, $chroot_dir) { + plog("ERROR: can't open $chroot_dir ($!)"); + return; + } + foreach (readdir($dir)) { + /$prefix/ or next; + clean_chroot_tmp($run, $chroot_dir, $_); + } + closedir $dir; +} + +sub clean_unionfs { + my ($unionfs_dir, $_run, $r, $union_id) = @_; + + -d "$unionfs_dir/unionfs.$r.$union_id" or return $union_id; + plog(2, "cleaning unionfs $unionfs_dir/unionfs.$r.$union_id"); + my $nok = 1; + my $path = "$unionfs_dir/unionfs.$r.$union_id"; + + while ($nok) { + $nok = 0; + foreach my $fs ([ 'proc', 'proc' ], [ 'dev/pts', 'devpts' ]) { + my ($dir, $type) = @$fs; + if (-d "$path/$dir" && check_mounted("$path/$dir", $type)) { + plog(1, "clean_unionfs: umounting $path/$dir\n"); + if (system("$sudo umount $path/$dir &>/dev/null")) { + plog("ERROR: could not umount $path/$dir"); + } + } + } + foreach my $t ('unionfs', 'tmpfs') { + # unfortunately quite oftem the unionfs is busy and could not + # be unmounted + + my $d = "$unionfs_dir/$t.$r.$union_id"; + if (-d $d && check_mounted($d, $t)) { + $nok = 1; + system("$sudo /sbin/fuser -k $d &> /dev/null"); + plog(3, "umounting $d"); + if (system(qq($sudo umount $d &> /dev/null))) { + plog(2, "WARNING: could not umount $d ($!)"); + return $union_id + 1; + } + } + } + } + + foreach my $t ('unionfs', 'tmpfs') { + my $d = "$unionfs_dir/$t.$r.$union_id"; + plog(2, "removing $d"); + if (system($sudo, 'rm', '-rf', $d)) { + plog("ERROR: removing $d failed ($!)"); + return $union_id + 1; + } + } + $union_id; +} + +sub clean_chroot_tmp { + my ($run, $chroot_dir, $dir) = @_; + my $d = "$chroot_dir/$dir"; + + foreach my $m ('proc', 'dev/pts') { + if (system("$sudo umount $d/$m &>/dev/null") && $run->{verbose} > 1) { + plog("ERROR: could not umount /$m in $d/"); + } + } + + plog(1, "cleaning $d"); + system("$sudo /sbin/fuser -k $d &> /dev/null"); + plog(1, "removing $d"); + system($sudo, 'rm', '-rf', $d); +} + +sub check_mounted { + my ($mount_point, $type) = @_; + + my $mount; + if (!open $mount, '/proc/mounts') { + plog("ERROR: could not open /proc/mounts"); + return; + } + $mount_point =~ s,//+,/,g; + local $_; + while (<$mount>) { + return 1 if /^\w+ $mount_point $type /; + } + 0; +} + +sub create_build_chroot { + my ($chroot, $chroot_tar, $run, $config) = @_; + create_chroot($chroot, $chroot_tar, $run, $config, + { packages => $config->{basesystem_packages} }); +} + +sub create_chroot { + my ($chroot, $chroot_tar, $run, $config, $opt) = @_; + my $tmp_tar = mktemp("$chroot_tar.tmp.XXXXXX"); + my $tmp_chroot = mktemp("$chroot.tmp.XXXXXX"); + my $rebuild; + my $clean = sub { + plog("Remove temporary chroot tarball"); + sudo($run, $config, '--rm', '-r', $tmp_chroot, $tmp_tar); + }; + + plog('NOTIFY', "creating chroot"); + plog('DEBUG', "... with packages " . join(', ', @{$opt->{packages}})); + + if (mkdir($tmp_chroot) && (!-f $chroot_tar || link $chroot_tar, $tmp_tar)) { + if (!-f $chroot_tar) { + plog("rebuild chroot tarball"); + $rebuild = 1; + if (!build_chroot($run, $config, $tmp_chroot, $chroot_tar, $opt)) { + $clean->(); + return; + } + } else { + plog('DEBUG', "decompressing /var/log/qa from $chroot_tar in $tmp_chroot"); + system($sudo, 'tar', 'xf', $chroot_tar, '-C', $tmp_chroot, "./var/log/qa"); + + my $qa; + if (open $qa, "$tmp_chroot/var/log/qa") { + my $ok; + my $f; + while (!$ok && ($f = <$qa>)) { + chomp $f; + if (!-f "$config->{basesystem_media_root}/media/$config->{basesystem_media}/$f") { + plog('DEBUG', "$f has changed"); + plog('NOTIFY', "Rebuilding chroot tarball"); + + $rebuild = 1; + sudo($run, $config, '--rm', '-r', $tmp_chroot); + mkdir $tmp_chroot; + if (!build_chroot($run, $config, $tmp_chroot, $chroot_tar, $opt)) { + $clean->(); + return; + } + $ok = 1; + } + } + } else { + plog('DEBUG', "can't open $tmp_chroot/var/log/qa"); + plog('ERR', "can't check chroot, recreating"); + + if (!build_chroot($run, $config, $tmp_chroot, $chroot_tar, $opt)) { + $clean->(); + return; + } + } + } + link $tmp_tar, $chroot_tar; + } else { + die "FATAL: could not initialize chroot ($!)\n"; + } + + if (!-d $chroot || $rebuild) { + plog('DEBUG', "recreate chroot $chroot"); + plog('NOTIFY', "recreate chroot"); + my $urpmi = $run->{urpmi}; + $urpmi->clean_urpmi_process($chroot); + sudo($run, $config, '--rm', '-r', $chroot, $tmp_tar); + mkdir_p $chroot; + system($sudo, 'tar', 'xf', $chroot_tar, '-C', $chroot); + plog('NOTIFY', "chroot recreated in $chroot_tar (live in $chroot)"); + } + + $clean->(); + + 1; +} + +sub build_chroot { + my ($run, $config, $tmp_chroot, $chroot_tar, $opt) = @_; + + plog('DEBUG', "building the chroot with " + . join(', ', @{$opt->{packages}})); + + sudo($run, $config, "--mkdir", "-p", "$tmp_chroot/dev/pts", + "$tmp_chroot/etc/sysconfig", "$tmp_chroot/proc"); + + # create empty files + foreach ('/etc/ld.so.conf', '/etc/mtab') { + system($sudo, 'touch', "$tmp_chroot$_"); + } + + system($sudo, 'mknod', "$tmp_chroot/dev/null", 'c', 1, 3); + system("$sudo chmod a+rw $tmp_chroot/dev/null"); + + #system(qq($sudo sh -c "echo 127.0.0.1 localhost > $tmp_chroot/etc/hosts")); + # warly some program perform a gethostbyname(hostname) and in the cluster the + # name are not resolved via DNS but via /etc/hosts + sudo($run, $config, '--cp', "/etc/hosts", "$tmp_chroot/etc/"); + sudo($run, $config, '--cp', "/etc/resolv.conf", "$tmp_chroot/etc/"); + + sudo($run, $config, "--initdb", $tmp_chroot); + # install chroot + my $urpmi = $run->{urpmi}; + $urpmi->set_command($tmp_chroot); + + # 20060826 warly urpmi --root does not work properly + $urpmi->install_packages( + "chroot", + $tmp_chroot, + $run->{local_spool}, + {}, + 'initialize', + "[ADMIN] creation of initial chroot failed on $run->{my_arch}", + { maintainer => $config->{admin} }, + @{$opt->{packages}} + ); + + if (-f "$tmp_chroot/bin/rpm") { + system($sudo, 'sh', '-c', "chroot $tmp_chroot rpm -qa --qf '\%{NAME}-\%{VERSION}-\%{RELEASE}.\%{ARCH}.rpm\n' > $tmp_chroot/var/log/qa"); + # + # CM: Choose a sub-500 uid to prevent collison with $luser + # + system($sudo, 'chroot', $tmp_chroot, 'adduser', '-o', '--uid', 499, 'builder'); + sudo($run, $config, "--mkdir", "-p", "$tmp_chroot/home/builder/rpm"); + foreach my $p (qw(RPMS BUILD SPECS SRPMS SOURCES tmp)) { + -d "$tmp_chroot/home/builder/rpm/$p" and next; + sudo($run, $config, "--mkdir", "$tmp_chroot/home/builder/rpm/$p"); + } + system($sudo, 'chown', '-R', 499, "$tmp_chroot/home/builder"); + sudo($run, $config, "--rm", "$tmp_chroot/var/lib/rpm/__db*"); + return !system($sudo, 'tar', 'czf', $chroot_tar, '-C', $tmp_chroot, '.'); + } +} + +sub check_build_chroot { + my ($chroot, $chroot_tar, $run, $config) = @_; + + check_chroot($chroot, $chroot_tar, $run, $config, + { packages => $config->{basesystem_packages} }); +} + +sub check_chroot { + my ($chroot, $chroot_tar, $run, $config, $opt) = @_; + + plog('DEBUG', "checking basesystem tar"); + + my (@stat) = stat $chroot_tar; + + if (time -$stat[9] > 604800) { + plog('WARN', "chroot tarball too old, force rebuild"); + sudo($run, $config, '--rm', '-r', $chroot, $chroot_tar); + } + create_chroot($chroot, $chroot_tar, $run, $config, $opt); +} + +sub clean_all_unionfs { + my ($run, $unionfs_dir) = @_; + + plog(2, "Cleaning old unionfs remaining dir in $unionfs_dir"); + + my $dir; + if (!opendir $dir, $unionfs_dir) { + plog(0, "FATAL could not open $unionfs_dir ($!)"); + return; + } + + foreach (readdir $dir) { + /unionfs\.((?:0\.)?\d+)\.(\d*)$/ or next; + clean_unionfs($unionfs_dir, $run, $1, $2); + } + + closedir $dir; +} + + +1; diff --git a/lib/Iurt/Config.pm b/lib/Iurt/Config.pm new file mode 100644 index 0000000..9efcd24 --- /dev/null +++ b/lib/Iurt/Config.pm @@ -0,0 +1,304 @@ +package Iurt::Config; + +use base qw(Exporter); +use RPM4::Header; +use Data::Dumper; +use MDK::Common; +use Iurt::Util qw(plog); +use strict; + +our @EXPORT = qw( + config_usage + config_init + get_date + dump_cache + dump_cache_par + init_cache + get_maint + get_date + get_prefix + get_repsys_conf + check_arch + check_noarch + get_package_prefix + %arch_comp +); + +our %arch_comp = ( + 'i586' => { 'i386' => 1, 'i486' => 1, 'i586' => 1 }, + 'i686' => { 'i386' => 1, 'i486' => 1, 'i586' => 1, 'i686' => 1 }, + 'x86_64' => { 'x86_64' => 1 }, + 'ppc' => { 'ppc' => 1 }, + 'ppc64' => { 'ppc' => 1, 'ppc64' => 1 }, +); + + +=head2 config_usage($config_usage, $config) + +Create an instance of a class at runtime. +I<$config_usage> is the configuration help, +I<%config> is the current configuration values +Return true. + +=cut + +sub config_usage { + my ($config_usage, $config) = @_; + print " + + Iurt configuration keywords: + +"; + $Data::Dumper::Indent = 0; + $Data::Dumper::Terse = 1; + foreach my $k (sort keys %$config_usage) { + print " $k: $config_usage->{$k}{desc} + default: ", Data::Dumper->Dump([ $config_usage->{$k}{default} ]), ", current: ", Data::Dumper->Dump([ $config->{$k} ]), "\n"; + } + print "\n\n"; +} + +=head2 config_init($config_usage, $config, $rung) + +Create an instance of a class at runtime. +I<$config_usage> is the configuration help, +I<%config> is the current configuration values +I<%run> is the current running options +Return true. + +=cut + +sub config_init { + my ($config_usage, $config, $run) = @_; + + foreach my $k (keys %$config_usage) { + ref $config_usage->{$k}{default} eq 'CODE' and next; + $config->{$k} ||= $run->{config}{$k} || $config_usage->{$k}{default}; + } + # warly 20061107 + # we need to have all the default initialised before calling functions, so this + # cannot be done in the same loop + foreach my $k (keys %$config_usage) { + ref $config_usage->{$k}{default} eq 'CODE' or next; + my $a = $config_usage->{$k}{default}($config, $run); + $config->{$k} ||= $run->{config}{$k} || $a; + } +} + + +=head2 get_date($shift) + +Create a string based on the current date and time +I<$shift> number of second to shift the date +Return date-time and date + +=cut + +sub get_date { + my ($o_shift) = @_; + my ($sec, $min, $hour, $mday, $mon, $year) = gmtime(time() - $o_shift); + $year += 1900; + my $fulldate = sprintf "%4d%02d%02d%02d%02d%02d", $year, $mon+1, $mday, $hour, $min, $sec; + my $daydate = sprintf "%4d%02d%02d", $year, $mon+1, $mday; + $fulldate, $daydate; +} + +sub get_prefix { + my ($luser) = @_; + my $hostname = `hostname`; + my ($fulldate) = get_date(); + my ($host) = $hostname =~ /([^.]*)/; + join('.', $fulldate, $luser, $host, $$) . '_'; +} + +sub get_package_prefix { + my ($rpm) = @_; + my ($prefix1) = $rpm =~ /^(\d{14}\.\w+\.\w+\.\d+)_/; + my ($prefix2) = $rpm =~ /^(\@\d+:)/; + "$prefix1$prefix2"; +} +=head2 init_cache($run, $config) + +Create a string based on the current date and time +I<%run> is the current running options +I<%config> is the current configuration values +Initialize the cache + +=cut + +sub init_cache { + my ($run, $config, $empty) = @_; + my $program_name = $run->{program_name}; + my $cachefile = "$config->{cache_home}/$program_name.cache"; + my $cache; + if (-f $cachefile) { + $run->{LOG}->("$program_name: loading cache file $cachefile\n"); + $cache = eval(cat_($cachefile)) or print "FATAL $program_name: could not load cache $cachefile ($!)\n"; + } else { + $cache = $empty; + } + $run->{cachefile} = $cachefile; + $run->{cache} = $cache; + $cache; +} + +=head2 dump_cache($run, $config) + +Create a string based on the current date and time +I<%run> is the current running options +Dump the cache + +=cut + +sub dump_cache { + my ($run) = @_; + my $program_name = $run->{program_name}; + my $filename = $run->{cachefile}; + my $cache = $run->{cache}; + my $daydate = $run->{daydate}; + open my $file, ">$filename.tmp.$daydate" or die "FATAL $program_name dump_cache: cannot open $filename.tmp"; + $Data::Dumper::Indent = 1; + $Data::Dumper::Terse = 1; + print $file Data::Dumper->Dump([ $cache ], [ "cache" ]); + # flock does not work on network files and lockf seems to fail too + $run->{LOG}->("$program_name: locking to dump the cache in $filename\n"); + if (-f "$filename.lock") { + $run->{LOG}->("ERROR iurt: manual file lock exist, do not save the cache\n"); + } else { + open my $lock, ">$filename.lock"; + print $lock $$; + close $lock; + unlink $filename; + link "$filename.tmp.$daydate", $filename; + unlink "$filename.lock"; + } +} + +# FIXME need to merge with the simpler dump_cache +sub dump_cache_par { + my ($run) = @_; + my $filename = $run->{cachefile}; + my $cache = $run->{cache}; + my $daydate = $run->{daydate}; + + # Right now there are no mechanism of concurrent access/write to the cache. There is + # on global lock for one iurt session. A finer cache access would allow several iurt running + # but the idea is more to have a global parrallel build than several local ones. + return if $run->{debug} || !$run->{use_cache}; + open my $file, ">$filename.tmp.$daydate" or die "FATAL iurt dump_cache: cannot open $filename.tmp"; + if ($run->{concurrent_run}) { + plog('DEBUG', "merging cache"); + my $old_cache; + if (-f $filename) { + plog('DEBUG', "loading cache file $filename"); + $old_cache = eval(cat_($filename)); + + foreach my $k ('rpm_srpm', 'failure', 'no_unionfs', 'queue', 'needed', 'warning', 'buildrequires') { + foreach my $rpm (%{$old_cache->{$k}}) { + $cache->{$k}{$rpm} ||= $old_cache->{$k}{$rpm}; + } + } + } + # $cache = { rpm_srpm => {}, failure => {}, queue => {}, warning => {}, run => 1, needed => {}, no_unionfs => {} } + } + $Data::Dumper::Indent = 1; + $Data::Dumper::Terse = 1; + print $file Data::Dumper->Dump([ $cache ], [ "cache" ]); + # flock does not work on network files and lockf seems to fail too + my $status = 1; #File::lockf::lock($file); + if (!$status) { + unlink $filename; + link "$filename.tmp.$daydate", $filename; + File::lockf::ulock($file); + } else { + plog('WARN', "WARNING: locking the cache file $filename failed (status $status $!), try to lock manually"); + if (-f "$filename.lock") { + plog('ERR', "ERROR: manual file lock exist, do not save the cache"); + } else { + open my $lock, ">$filename.lock"; + print $lock $$; + close $lock; + unlink $filename; + link "$filename.tmp.$daydate", $filename; + unlink "$filename.lock"; + } + } +} + +sub get_maint { + my ($run, $srpm) = @_; + my ($srpm_name) = $srpm =~ /(.*)-[^-]+-[^-]+\.[^.]+$/; + $srpm_name ||= $srpm; + if ($run->{maint}{$srpm}) { + return $run->{maint}{$srpm}, $srpm_name; + } + my $maint = `rpmmon -s -p "$srpm_name"`; + chomp $maint; + $run->{maint}{$srpm} = $maint; + $maint, $srpm_name; +} + +sub get_repsys_conf { + my ($file) = @_; + open my $fh, $file or return; + my %mail; + my $ok; + local $_; + while (<$fh>) { + if (/\[users\]/) { + $ok = 1; + } elsif (/\[/) { + $ok = 0; + } + $ok or next; + my ($user, $mail) = split " = "; + chomp $mail; + $mail{$user} = $mail; + $mail or next; + } + \%mail; +} + +sub check_noarch { + my ($rpm) = @_; + my $hdr = RPM4::Header->new($rpm); + + # Stupid rpm doesn't return an empty list so we must check for (none) + + my ($build_archs) = $hdr->queryformat('%{BUILDARCHS}'); + + if ($build_archs ne '(none)') { + ($build_archs) = $hdr->queryformat('[%{BUILDARCHS} ]'); + my @list = split ' ', $build_archs; + return 1 if member('noarch', @list); + } + + return 0; +} + +sub check_arch { + my ($rpm, $arch) = @_; + my $hdr = RPM4::Header->new($rpm); + + # Stupid rpm doesn't return an empty list so we must check for (none) + + my ($exclusive_arch) = $hdr->queryformat('%{EXCLUSIVEARCH}'); + + if ($exclusive_arch ne '(none)') { + ($exclusive_arch) = $hdr->queryformat('[%{EXCLUSIVEARCH} ]'); + my @list = split ' ', $exclusive_arch; + return 0 unless any { $arch_comp{$arch}{$_} } @list; + } + + my ($exclude_arch) = $hdr->queryformat('[%{EXCLUDEARCH} ]'); + + if ($exclude_arch ne '(none)') { + ($exclude_arch) = $hdr->queryformat('[%{EXCLUDEARCH} ]'); + my @list = split ' ', $exclude_arch; + return 0 if member($arch, @list); + } + + return 1; +} + +1; diff --git a/lib/Iurt/DKMS.pm b/lib/Iurt/DKMS.pm new file mode 100644 index 0000000..5ab46c8 --- /dev/null +++ b/lib/Iurt/DKMS.pm @@ -0,0 +1,318 @@ +package Iurt::DKMS; + +use strict; +use base qw(Exporter); +use MDV::Distribconf::Build; +use Iurt::Chroot qw(clean_chroot add_local_user dump_rpmmacros); +use Iurt::Config qw(get_maint get_prefix dump_cache init_cache); +use File::NCopy qw(copy); +use Iurt::Process qw(sudo); +use Iurt::Util qw(plog); +use RPM4::Header; +use MDK::Common; + +our @EXPORT = qw( + search_dkms + dkms_compile +); + +sub new { + my ($class, %opt) = @_; + my $self = bless { + config => $opt{config}, + run => $opt{run}, + }, $class; + + $self; +} + +=head2 search_dkms($run, $config) + +Search for dkms packages which needs to be recompiled for new kernel +I<$run> is the running environment +I<%config> is the current configuration values +Return true. + +=cut + +sub search_dkms { + my ($self) = @_; + my $config = $self->{config}; + my $run = $self->{run}; + my $arch = $run->{my_arch}; + my $root = $config->{repository}; + my $distro = $run->{distro}; + my $cache = $run->{cache}; + my $path = "$root/$distro/$arch"; + if (!-d $path) { + plog('ERR', "ERROR: $path is not a directory"); + return; + } + my $distrib = MDV::Distribconf::Build->new($path); + plog("getting media config from $path"); + if (!$distrib->loadtree) { + plog('ERR', "ERROR: $path does not seem to be a distribution tree"); + return; + } + $distrib->parse_mediacfg; + my %dkms; + my @kernel; + my %modules; + my %kernel_source; + foreach my $media ($distrib->listmedia) { + $media =~ /(SRPMS|debug_)/ and next; + my $path = $distrib->getfullpath($media, 'path'); + my $media_ok = $run->{dkms}{media} ? $media =~ /$run->{dkms}{media}/ : 1; + my $kmedia_ok = $run->{dkms}{kmedia} ? $media =~ /$run->{dkms}{kmedia}/ : 1; + plog("searching in $path"); + opendir my $rpmdh, $path; + foreach my $rpm (readdir $rpmdh) { + if ($rpm =~ /^dkms-(.*)-([^-]+-[^-]+)\.[^.]+\.rpm/) { + # we only check for kernel or modules in this media + $media_ok or next; + my $hdr = RPM4::Header->new("$path/$rpm"); + my $files = $hdr->queryformat('[%{FILENAMES} ])'); + my ($name, $version) = ($1, $2); + my ($modulesourcedir) = $files =~ m, /usr/src/([^/ ]+),; + my $script = $hdr->queryformat('%{POSTIN})'); + my ($realversion) = $script =~ / -v (\S+)/; + plog('NOTIFY', "dkms $name version $version source $modulesourcedir realversion $realversion"); + push @{$dkms{$media}}, [ $name, $version, $modulesourcedir, $realversion, "$path/$rpm" ]; + } elsif ($rpm =~ /^kernel-((?:[^-]+-)?[^-]+.*)-[^-]+-[^-]+\.[^.]+\.rpm/ && $rpm !~ /win4lin|latest|debug|stripped|BOOT|xen|doc/) { + # we do not check for kernel in this media + $kmedia_ok or next; + my $hdr = RPM4::Header->new("$path/$rpm"); + my $files = $hdr->queryformat('[%{FILENAMES} ])'); + my $version = $1; + if ($version =~ /(.*)source-(.*)/) { + my $source = "$1$2"; + my ($sourcedir) = $files =~ m, /usr/src/([^/ ]+),; + plog('NOTIFY', "kernel source $version ($source sourcedir $sourcedir)"); + $kernel_source{$source} = [ $version, $sourcedir ]; + } else { + my ($modulesdir) = $files =~ m, /lib/modules/([^/ ]+),; + plog('NOTIFY', "kernel $version (modules dir $modulesdir)"); + push @kernel, [ $version, $modulesdir ]; + } + } elsif ($rpm =~ /^(.*)-kernel-([^-]+-[^-]+.*)-([^-]+-[^-]+)\.[^.]+\.rpm$/) { + plog('NOTIFY', "modules $1 version $3 for kernel $2"); + # module version kernel + $modules{$1}{$3}{$2} = 1; + } + } + } + my $nb; + foreach my $media (keys %dkms) { + foreach my $dkms (@{$dkms{$media}}) { + my ($module, $version, $modulesourcedir, $realversion, $file) = @$dkms; + foreach my $k (@kernel) { + my ($kernel, $modulesdir) = @$k; + plog("checking $module-kernel-$modulesdir-$realversion"); + next if $cache->{dkms}{"$module-kernel-$modulesdir-$realversion"} && !$run->{ignore_failure}; + if (!$modules{$module}{$version}{$modulesdir}) { + my ($name, $v) = $kernel =~ /^([^-]+)-.*-(2\..*)/; + my $source = "$name-$v"; + if (!$kernel_source{$source}) { + my ($name) = $kernel =~ /(2\..*)/; + plog('ERR', "ERROR: no source for kernel $kernel (source $source), testing $name"); + $source = $name; + if (!$kernel_source{$source}) { + my $name = $kernel; + plog('ERR', "ERROR: no source for kernel $kernel (source $source), testing $name"); + $source = $name; + if (!$kernel_source{$source}) { + plog('ERR', "ERROR: no source for kernel $kernel (source $source), ignoring"); + next; + } + } + } + plog("dkms module $module version $version should be compiled for kernel $kernel ($source)"); + $nb++; + push @{$run->{dkms_todo}}, [ $module, $version, $modulesourcedir, $realversion, $file, $kernel, $modulesdir, @{$kernel_source{$source}}, $media ]; + } + $modules{$module}{$version}{$modulesdir}++; + } + } + } + foreach my $module (keys %modules) { + foreach my $version (keys %{$modules{$module}}) { + foreach my $modulesdir (keys %{$modules{$module}{$version}}) { + next if $modules{$module}{$version}{$modulesdir} < 2; + plog('WARN', "dkms module $module version $version for kernel $modulesdir is obsolete"); + push @{$run->{dkms_obsolete}}, "$module-kernel-$modulesdir-$version"; + } + } + } + $nb; +} + +=head2 dkms_compile($class, $local_spool, $done) + +Compile the dkms against the various provided kernel +Return true. + +=cut + +sub dkms_compile { + my ($self, $local_spool, $done) = @_; + my $config = $self->{config}; + my $run = $self->{run}; + my $urpmi = $run->{urpmi}; + # For dkms build, the chroot is only installed once and the all the modules are recompiled + my $chroot_tmp = $run->{chroot_tmp}; + my $chroot_tar = $run->{chroot_tar}; + my $cache = $run->{cache}; + my $luser = $run->{user}; + my $to_compile = $run->{to_compile}; + + plog("building chroot: $chroot_tmp"); + clean_chroot($chroot_tmp, $chroot_tar, $run, $config); + my %installed; + # initialize urpmi command + $urpmi->urpmi_command($chroot_tmp, $luser); + # also add macros for root + add_local_user($chroot_tmp, $run, $config, $luser, $run->{uid}); + + if (!dump_rpmmacros($run, $config, "$chroot_tmp/home/$luser/.rpmmacros") || !dump_rpmmacros($run, $config, "$chroot_tmp/root/.rpmmacros")) { + plog('ERR', "ERROR: adding rpmmacros failed"); + return; + } + + my $kerver = `uname -r`; + chomp $kerver; + + my $dkms_spool = "$local_spool/dkms/"; + -d $dkms_spool or mkdir $dkms_spool; + + for (my $i; $i < @{$run->{dkms_todo}}; $i++) { + my ($name, $version, $_modulesourcedir, $realversion, $file, $kernel, $modulesdir, $source, $sourcedir, $media) = @{$run->{dkms_todo}[$i]}; + $done++; + + plog("dkms modules $name version $version for kernel $kernel [$done/$to_compile]"); + + # install kernel and dkms if not already installed + my $ok = 1; + # some of the dkms modules does not handle correclty the -k option and use uname -r to + # find kernel modules dir. + # FIXME must send a mail to the maintainer for that problem + # try to workarround with a symlink + if ($kerver ne $modulesdir) { + if (-e "$chroot_tmp/lib/modules/$kerver") { + system("sudo mv $chroot_tmp/lib/modules/$kerver $chroot_tmp/lib/modules/$kerver.tmp"); + } + if (system("sudo ln -sf $modulesdir $chroot_tmp/lib/modules/$kerver")) { + plog('ERR', "ERROR: creating a link from $chroot_tmp/lib/modules/$modulesdir to $kerver failed ($!)"); + next; + } + } + foreach my $pkg ("kernel-$source", "dkms", "kernel-$kernel", $file) { + my $pkgname = basename($pkg); + if ($run->{chrooted_urpmi} && -f $pkg) { + copy $pkg, "$chroot_tmp/tmp/"; + $pkg = "/tmp/$pkgname"; + } + if (!$installed{$pkg}) { + plog('DEBUG', "install package: $pkg"); + if (!$urpmi->install_packages("dkms-$name-$version", $chroot_tmp, $local_spool, {}, "dkms_$pkgname", "[DKMS] package $pkg installation error", { maintainer => $config->{admin} }, $pkg)) { + plog('ERR', "ERROR: error installing package $pkg"); + $ok = 0; + last; + } + $installed{$pkg} = 1; + } + # recreate the appropriate kernel source link + } + $ok or next; + + plog('DEBUG', "symlink from /lib/modules/$modulesdir/build to /usr/src/$sourcedir"); + + if (system("sudo ln -sf /usr/src/$sourcedir $chroot_tmp/lib/modules/$modulesdir/build")) { + plog('ERR', "linking failed ($!)"); + next; + } + # seems needed for some kernel + system("cd $chroot_tmp/usr/src/$sourcedir && sudo make prepare"); + # If the dkms packages get installed, the modules is correclty built + # but if we just compile it for a new kernel, we need to rebuild it manually + + foreach my $cmd ('add', 'build') { + my $command = "TMP=/home/$luser/tmp/ sudo chroot $chroot_tmp /usr/sbin/dkms $cmd -m $name -v $realversion --rpm_safe_upgrade -k $modulesdir --kernelsourcedir=/usr/src/$sourcedir"; + plog('DEBUG', "execute: $command"); + system($command); + } + + # now need to move dkms build if it wrongly assume a build for the running kernel + plog("search module in /var/lib/dkms/$name/$version/$kerver/"); + if (-d "$chroot_tmp/var/lib/dkms/$name/$version/$kerver/") { + system("sudo mv $chroot_tmp/var/lib/dkms/$name/$realversion/$kerver/ $chroot_tmp/var/lib/dkms/$name/$realversion/$modulesdir/"); + } + $cache->{dkms}{"$name-kernel-$modulesdir-$realversion"} = 1; + if (system("sudo chroot $chroot_tmp /usr/sbin/dkms mkrpm -m $name -v $realversion --rpm_safe_upgrade -k $modulesdir")) { + plog('FAIL', "build failed ($!)"); + next; + } + + plog('OK', "build succesful, copy packages to $dkms_spool/$media"); + + -d "$dkms_spool/$media" or mkdir_p "$dkms_spool/$media"; + + system("cp $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm $dkms_spool/$media/ &>/dev/null") && system("cp $chroot_tmp/usr/src/rpm/RPMS/*/*.rpm $dkms_spool/$media/ &>/dev/null") and $run->{LOG}->("ERROR: could not copy dkms packages from $chroot_tmp/usr/src/rpm/RPMS/*/*.rpm or $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm to $dkms_spool/$media ($!)\n"); + !sudo($run, $config, '--rm', "$chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm") || !sudo($run, $config, '--rm', "$chroot_tmp/usr/src/rpm/RPMS/*/*.rpm") and $run->{LOG}->("ERROR: could not delete dkms packages from $chroot_tmp/usr/src/rpm/RPMS/*/*.rpm or $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm ($!)\n"); + + if ($kerver ne $modulesdir) { + system("sudo rm -f $kerver $chroot_tmp/lib/modules/$modulesdir"); + if (-e "$chroot_tmp/lib/modules/$kerver.tmp") { + system("sudo mv $chroot_tmp/lib/modules/$kerver.tmp $chroot_tmp/lib/modules/$kerver"); + } + } + process_dkms_queue($self, 0, 0, $media, "$dkms_spool/$media"); + # compile dkms modules + } + dump_cache($run); + $done; +} +# FIXME will replace the iurt2 process_qeue when youri-queue is active +sub process_dkms_queue { + my ($self, $wrong_rpm, $quiet, $media, $dir) = @_; + my $run = $self->{run}; + return if !$run->{upload} && $quiet; + my $config = $self->{config}; + my $cache = $run->{cache}; + $media ||= $run->{media}; + my $urpmi = $run->{urpmi}; + + $dir ||= "$config->{local_upload}/iurt/$run->{distro_tag}/$run->{my_arch}/$media}/"; + + plog("processing $dir"); + opendir my $rpmdir, $dir or return; + # get a new prefix for each package so that they will not be all rejected if only one is wrong + my $prefix = get_prefix('iurt'); + foreach my $rpm (readdir $rpmdir) { + my ($rarch, $srpm) = $urpmi->update_srpm($dir, $rpm, $wrong_rpm); + $rarch or next; + plog('DEBUG', $rpm); + next if !$run->{upload}; + + plog("copy $rpm to $config->{upload_queue}/$run->{distro}/$media/"); + + # recheck if the package has not been uploaded in the meantime + my $rpms_dir = "$config->{repository}/$run->{distro}/$run->{my_arch}/media/$media/"; + if (! -f "$rpms_dir/$rpm") { + my $err = system("/usr/bin/scp", "$dir/$rpm", $config->{upload_queue} . "/$run->{distro}/$media/$prefix$rpm"); + # try to keep the opportunity to prevent disk full " + if ($err) { + #$run->{LOG}->("ERROR process_queue: cannot copy $dir/$rpm to ", $config->{upload_queue}, "/$run->{distro}/$media/$prefix$rpm ($!)\n"); + next; + } + } + if ($run->{upload_source}) { + #should not be necessary + } + # should not be necessary to use sudo + sudo($run, $config, '--rm', "$dir/$rpm"); + $cache->{queue}{$srpm} = 1; + } + closedir $rpmdir; +} + +1; diff --git a/lib/Iurt/File.pm b/lib/Iurt/File.pm new file mode 100644 index 0000000..ba5f718 --- /dev/null +++ b/lib/Iurt/File.pm @@ -0,0 +1,68 @@ +package Iurt::File; + +use base qw(Exporter); +use Iurt::Util qw(plog); +use strict; + +our @EXPORT = qw( + check_upload_tree +); + +=head2 config_usage($config_usage, $config) + +Create an instance of a class at runtime. +I<$config_usage> is the configuration help, +I<%config> is the current configuration values +Return true. + +=cut + +sub check_upload_tree { + my ($_run, $todo, $func, $post) = @_; + + # Squash double slashes for cosmetics + $todo =~ s!/+!/!g; + + opendir my $dir, $todo; + plog('INFO', "check dir: $todo"); + + foreach my $f (readdir $dir) { + $f =~ /^\.{1,2}$/ and next; + if (-d "$todo/$f") { + plog('DEBUG', "checking target $todo/$f"); + opendir my $target_dir, "$todo/$f"; + + foreach my $m (readdir $target_dir) { + $m =~ /^\.{1,2}$/ and next; + if (-d "$todo/$f/$m") { + plog('DEBUG', "checking media $todo/$f/$m"); + opendir my $media_dir, "$todo/$f/$m"; + + foreach my $s (readdir $media_dir) { + $s =~ /^\.{1,2}$/ and next; + if (-d "$todo/$f/$m/$s") { + if ($func) { + opendir my $submedia_dir, "$todo/$f/$m/$s"; + foreach my $r (readdir $submedia_dir) { + $r =~ /^\.{1,2}$/ and next; + $func->($todo, $f, $m, $s, $r); + } + } + # cleaning + if ($post) { + opendir my $submedia_dir, "$todo/$f/$m/$s"; + foreach my $r (readdir $submedia_dir) { + $r =~ /^\.{1,2}$/ and next; + $post->($todo, $f, $m, $s, $r); + } + } + } else { + # may need to check also here for old target + } + } + } + } + } + } +} + diff --git a/lib/Iurt/Mail.pm b/lib/Iurt/Mail.pm new file mode 100644 index 0000000..b805db2 --- /dev/null +++ b/lib/Iurt/Mail.pm @@ -0,0 +1,27 @@ +package Iurt::Mail; + +use strict; +use MIME::Words qw(encode_mimewords); +use base qw(Exporter); + +our @EXPORT = qw( + sendmail +); + +sub sendmail { + my ($to, $cc, $subject, $text, $from, $debug) = @_; + do { print "Cannot find sender-email-address [$to]\n"; return } unless defined($to); + my $MAIL; + if (!$debug) { open $MAIL, "| /usr/sbin/sendmail -t" or return } else { open $MAIL, ">&STDOUT" or return } + my $sender = encode_mimewords($to); + $subject = encode_mimewords($subject); + print $MAIL "To: $sender\n"; + if ($cc) { $cc = encode_mimewords($cc); print $MAIL "Cc: $cc\n" } + print $MAIL "From: $from\n"; + print $MAIL "Subject: $subject\n"; + print $MAIL "\n"; + print $MAIL $text; + close($MAIL); +} + +1 diff --git a/lib/Iurt/Process.pm b/lib/Iurt/Process.pm new file mode 100644 index 0000000..6982a65 --- /dev/null +++ b/lib/Iurt/Process.pm @@ -0,0 +1,355 @@ +package Iurt::Process; + +use strict; +use base qw(Exporter); +use MDK::Common; +use Filesys::Df qw(df); +use Iurt::Mail qw(sendmail); +use Iurt::Config qw(dump_cache_par); +use Iurt::Util qw(plog); +use POSIX ":sys_wait_h"; + +our @EXPORT = qw( + kill_for_good + clean_process + check_pid + clean + perform_command + sudo +); + +=head2 config_usage($program_name, $run) + +Check that there is no other program running and create a pidfile lock +I<$run> current running options +Return true. + +=cut + +# CM: this actually doesn't offer race-free locking, a better system +# should be designed + +sub check_pid { + my ($run) = @_; + + my $pidfile = "$run->{pidfile_home}/$run->{pidfile}"; + + # Squash double slashes for cosmetics + $pidfile =~ s!/+!/!g; + + plog('DEBUG', "check pidfile: $pidfile"); + + if (-f $pidfile) { + my (@stat) = stat $pidfile; + + open my $test_PID, $pidfile; + my $pid = <$test_PID>; + close $test_PID; + + if (!$pid) { + plog('ERR', "ERROR: invalid pidfile ($pid), should be <pid>"); + unlink $pidfile; + } + + if ($pid && getpgrp $pid != -1) { + my $time = $stat[9]; + my $state = `ps h -o state $pid`; + chomp $state; + + if ($time < time()-7200 || $state eq 'Z') { + my $i; + + plog('WARN', "another instance [$pid] is too old, killing it"); + + while ($i < 5 && getpgrp $pid != -1) { + kill_for_good($pid); + $i++; + sleep 1; + } + } else { + plog("another instance [$pid] is already running for ", + time()-$time, " seconds"); + exit(); + } + } else { + plog('WARN', "cleaning stale lockfile"); + unlink $pidfile; + } + } + + open my $PID, ">$pidfile" + or die "FATAL: can't open pidfile $pidfile for writing"; + + print $PID $$; + close $PID; + $pidfile; +} + +=head2 perform_command($command, $run, $config, $cache, %opt) + +Run a command and check various running parameters such as log size, timeout... +I<$command> the command to run +I<$run> current running options +I<$config> the configuration +I<$cache> cached values +I<%opt> the options for the command run +Return true. + +=cut + +sub perform_command { + my ($command, $run, $config, $cache, %opt) = @_; + + $opt{timeout} ||= 300; + $opt{freq} ||= 24; + $opt{type} ||= 'shell'; + + plog('DEBUG', "Timeout $opt{timeout}"); + + my ($output, $fulloutput, $comment); + my ($kill, $pipe); + + if ($opt{debug}) { + if ($opt{type} eq 'perl') { + print "Would run perl command with timeout = $opt{timeout}\n"; + } else { + print "Would run $command with timeout = $opt{timeout}\n"; + } + return 1; + } + + local $SIG{PIPE} = sub { print "Broken pipe!\n"; $pipe = 1 }; + + my $retry = $opt{retry} || 1; + my $call_ret = 1; + my ($err, $pid, $try); + my $logfile = "$opt{log}/$opt{logname}.$run->{run}.log"; + my $max_retry = $config->{max_command_retry} < $retry ? + $retry : $config->{max_command_retry}; + + while ($retry) { + $try++; + if ($opt{retry} > 1) { + $logfile = "$opt{log}/$opt{logname}-$try.$run->{run}.log"; + } + if ($opt{log}) { + my $parent_pid = $$; + $pid = fork(); + #close STDIN; close STDERR;close STDOUT; + my $tot_time; + if (!$pid) { + plog('DEBUG', "Forking to monitor log size"); + $run->{main} = 0; + local $SIG{ALRM} = sub { exit() }; + $tot_time += sleep 30; + my $size_limit = $config->{log_size_limit}; + $size_limit =~ s/k/000/i; + $size_limit =~ s/M/000000/i; + $size_limit =~ s/G/000000000/i; + while ($tot_time < $opt{timeout}) { + my (@stat) = stat $logfile; + if ($stat[7] > $size_limit) { + plog('WARN', "WARNING: killing current command because of log size exceeding limit ($stat[7] > $config->{log_size_limit})"); + kill 14, "-$parent_pid"; + exit(); + } + my $df = df $opt{log}; + if ($df->{per} == 100) { + plog('WARN', "WARNING: killing current command because running out of disk space (only $df->{bavail}KB left)"); + kill 14, "-$parent_pid"; + exit(); + } + $tot_time += sleep 30; + } + exit(); + } + } + + eval { + local $SIG{ALRM} = sub { + print "Timeout!\n"; + $kill = 1; + die "alarm\n"; # NB: \n required + }; + + alarm $opt{timeout}; + + if ($opt{type} eq 'perl') { + plog('DEBUG', "perl command"); + $command->[0](@{$command->[1]}); + } else { + plog('DEBUG', $command); + if ($opt{log}) { + #$output = `$command 2>&1 2>&1 | tee $opt{log}/$opt{hash}.$run.log`; + system("$command &> $logfile"); + } else { + $output = `$command 2>&1`; + } + } + alarm 0; + }; + + $err = $?; + $err = 0 if any { $_ == $err } @{$opt{error_ok}}; + + # kill pid watching log file size + if ($pid) { + kill_for_good($pid); + } + + if ($@) { # timed out + # propagate unexpected errors + die "FATAL: unexpected signal ($@)" unless $@ eq "alarm\n"; + } + + # Keep the run first on the harddrive so that one can check the + # command status tailing it + if ($opt{log} && open my $log, $logfile) { + local $/; + $output = <$log>; + } + + $fulloutput .= $output; + if (ref $opt{callback}) { + $call_ret = $opt{callback}(\%opt, $output); + $call_ret == -1 and return 1; + $call_ret == -2 and return 0; + } + + if ($kill && $opt{type} ne 'shell') { + $comment = "Command killed after $opt{timeout}s: $command\n"; + my ($cmd_to_kill) = $command =~ /sudo(?: chroot \S+)? (.*)/; + clean_process($run, $cmd_to_kill, $run->{verbose}); + } elsif ($pipe) { + $comment = "Command received SIGPIPE: $command\n"; + sendmail($config->{admin}, '' , + "$opt{hash} on $run->{my_arch} for $run->{media}: broken pipe", + "$comment\n$output", "Iurt the build bot <$config->{admin}>", + $opt{debug_mail}); + } else { + if ($opt{type} eq 'shell') { + $comment = "Command failed: $command\n"; + } else { + $comment = "Command failed: $opt{type}\n"; + } + } + + # Maybe this has to be put before all the commands altering the + # $output var + + my $inc; + if ($opt{wait_regexp}) { + foreach my $wr (keys %{$opt{wait_regexp}}) { + if ($output =~ /$wr/m) { + if (ref $opt{wait_regexp}{$wr}) { + $inc = $opt{wait_regexp}{$wr}(\%opt, $output); + } + plog('ERR', "ERROR: $wr !"); + + sendmail($config->{admin}, '' , "$opt{hash} on $run->{my_arch} for $run->{media}: could not proceed", "$wr\n\n$comment\n$output", "Iurt the rebuild bot <$config->{admin}>", $opt{debug_mail}) if $opt{wait_mail}; + } + } + } + + if ($inc && $try < $max_retry) { + $retry += $inc; + } elsif ($call_ret && !$kill && !$err && !$opt{error_regexp} || $fulloutput !~ /$opt{error_regexp}/) { + $retry = 0; + } else { + $retry--; + } + } + + if (!$call_ret || $kill || $err || $opt{error_regexp} && $fulloutput =~ /$opt{error_regexp}/) { + + plog('ERR', "ERROR: call_ret=$call_ret kill=$kill err=$err ($opt{error_regexp})"); + + if ($opt{log} && $config->{log_url}) { + $comment = qq(See $config->{log_url}/$run->{distro_tag}/$run->{my_arch}/$run->{media}/log/$opt{srpm}/\n\n$comment); + } + + my $out; + if (length $fulloutput < 10000) { + $out = $fulloutput; + } else { + $out = "Message too big, see http link for details\n"; + } + + if ($opt{mail} && $config->{sendmail} && !$config->{no_mail}{$opt{mail}}) { + if (! ($cache->{warning}{$opt{hash}}{$opt{mail}} % $opt{freq})) { + my $cc = join ',', grep { !$config->{no_mail}{$_} } split ',', $opt{cc}; + sendmail($opt{mail}, $cc, $opt{error} , "$comment\n$out", "Iurt the rebuild bot <$config->{admin}>", $opt{debug_mail}); + } elsif ($config->{admin}) { + sendmail($config->{admin}, '' , $opt{error}, "$comment\n$out", "Iurt the rebuild bot <$config->{admin}>", $opt{debug_mail}); + } + } + $cache->{warning}{$opt{hash}}{$opt{mail}}++; + plog('FAIL', $comment); + plog('WARN', "--------------- Command failed, full output follows ---------------"); + plog('INFO', $fulloutput); + plog('WARN', "--------------- end of command output ---------------"); + + if ($opt{die}) { + dump_cache_par($run); + die "FATAL: $opt{error}."; + } + return 0; + } + 1; +} + +sub clean_process { + my ($run, $match, $verbose) = @_; + return clean($run, $match, "pgrep -u root -f", "sudo pkill -9 -u root -f", $verbose); +} + +sub clean { + my ($_run, $var, $cmd, $kill_cmd, $_verbose) = @_; + + plog('DEBUG', "clean command $var"); + $var or die "FATAL: no command given\n."; + + my $ps; + my $i; + + while ($ps = `$cmd "$var"`) { + system(qq($kill_cmd "$var" &>/dev/null)); + sleep 1; + $ps =~ s/\n/,/g; + plog('WARN', "Trying to remove previous blocked processes for $var ($ps)"); + waitpid(-1, POSIX::WNOHANG); + return 0 if $i++ > 10; + } + 1; +} + +sub kill_for_good { + my ($pid) = @_; + kill 14, $pid; + sleep 1; + waitpid(-1, POSIX::WNOHANG); + if (getpgrp $pid != -1) { + kill 15, $pid; + sleep 1; + waitpid(-1, POSIX::WNOHANG); + if (getpgrp $pid != -1) { + print STDERR "WARNING: have to kill -9 pid $pid\n"; + kill 9, $pid; + sleep 1; + waitpid(-1, POSIX::WNOHANG); + } + } +} + +sub sudo { + my ($_run, $config, @arg) = @_; + + #plog("Running $config->{iurt_root_command} @arg"); + + -x $config->{iurt_root_command} + or die "FATAL: $config->{iurt_root_command} command not found"; + + !system('/usr/bin/sudo', $config->{iurt_root_command}, @arg); +} + +1 diff --git a/lib/Iurt/Urpmi.pm b/lib/Iurt/Urpmi.pm new file mode 100644 index 0000000..f4628ee --- /dev/null +++ b/lib/Iurt/Urpmi.pm @@ -0,0 +1,789 @@ +package Iurt::Urpmi; + +use strict; +use RPM4::Header; +use File::Basename; +use File::NCopy qw(copy); +use MDV::Distribconf::Build; +use Iurt::Chroot qw(add_local_user create_temp_chroot check_build_chroot); +use Iurt::Process qw(perform_command clean clean_process sudo); +use Iurt::Config qw(dump_cache_par get_maint get_package_prefix); +use Iurt::Util qw(plog); + + +sub new { + my ($class, %opt) = @_; + my $self = bless { + config => $opt{config}, + run => $opt{run}, + urpmi_options => $opt{urpmi_options}, + }, $class; + my $config = $self->{config}; + my $run = $self->{run}; + + if ($run->{use_system_distrib}) { + $config->{basesystem_media_root} ||= $run->{use_system_distrib}; + } elsif ($run->{chrooted_urpmi}) { + my ($host) = $run->{chrooted_urpmi}{rooted_media} =~ m,(?:file|http|ftp)://([^/]*),; + my ($_name, $_aliases, $_addrtype, $_length, @addrs) = gethostbyname($host); + + my $ip = join('.', unpack('C4', $addrs[0])); + + $ip =~ /\d+\.\d+\.\d+\.\d+/ + or die "FATAL: could not resolve $host ip address"; + + $run->{chrooted_urpmi}{rooted_media} =~ s/$host/$ip/; + $run->{chrooted_media} = $run->{chrooted_urpmi}{rooted_media} . + "/$run->{distro}/$run->{my_arch}"; + + # Now squash all slashes that don't follow colon + $run->{chrooted_media} =~ s|(?<!:)/+|/|g; + + plog('DEBUG', "installation media: $run->{chrooted_media}"); + } + $self->{urpmi_media_options} .= + " --use-distrib $config->{repository}/$run->{distro}/$run->{my_arch}"; + + $self; +} + +sub set_command { + my ($self, $chroot_tmp) = @_; + $self->{urpmi_command} = "urpmi $self->{urpmi_options} $self->{urpmi_media_options} --root $chroot_tmp"; +} + +sub set_local_media { + my ($self, $local_media) = @_; + $self->{local_media} = $local_media; +} + +sub add_to_local_media { + my ($self, $chroot_tmp, $srpm, $luser) = @_; + my $local_media = $self->{local_media}; + + system("cp $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm $local_media &>/dev/null") and plog("ERROR: could not copy rpm files from $chroot_tmp/home/$luser/rpm/RPMS/ to $local_media ($!)"); + system("cp $chroot_tmp/home/$luser/rpm/SRPMS/$srpm $local_media &>/dev/null") and plog("ERROR: could not copy $srpm from $chroot_tmp/home/$luser/rpm/SRPMS/ to $local_media ($!)"); +} + +sub urpmi_command { + my ($self, $chroot_tmp, $_luser) = @_; + my $run = $self->{run}; + my $local_media = $self->{local_media}; + + #plog(3, "urpmi_command ($chroot_tmp user $luser)"); + if ($run->{chrooted_urpmi}) { + $self->{urpmi_command} = "urpmi $self->{urpmi_options} $self->{urpmi_media_options} --root $chroot_tmp "; + +# CM: commented out +# this was causing rpm database corruption problems and the packages +# are already installed +# +# if (!install_packages($self, 'chroot', $chroot_tmp, $local_spool, {}, 'configure', "[ADMIN] installation of urpmi and sudo failed in the chroot $run->{my_arch}", { maintainer => $config->{admin}, check => 1 }, 'urpmi', 'sudo')) { +# $run->{chrooted_urpmi} = 0; +# return +# } + + # Here should be added only the needed media for the given package + # main/release -> main/release + # main/testing -> main/release main/testing + # contrib/release -> contrib/release main/release + # contrib/testing -> contrib/testing contrib/release main/testing main/release + # non-free/release ... + # This is now done with an option in iurt2 --chrooted-urpmi -m media1 media2 -- media_url + + if ($run->{chrooted_urpmi}{media}) { + foreach my $m (@{$run->{chrooted_urpmi}{media}}) { + my $m_name = $m; + $m_name =~ s,/,_,g; + if (!add_media($self, $chroot_tmp, $m_name, + "$m_name $run->{chrooted_media}/media/$m")) { + $run->{chrooted_urpmi} = 0; + return; + } + } + } else { + if (!add_media($self, $chroot_tmp, 'Main', "--distrib $run->{chrooted_media}")) { + if (!add_media($self, $chroot_tmp, 'Main', "--wget --distrib $run->{chrooted_media}")) { + $run->{chrooted_urpmi} = 0; + return; + } + } + } + + if (-d "$local_media/hdlist.cz") { + mkdir("$chroot_tmp/iurt_media/"); + opendir my $dir, $local_media; + my $next; + foreach my $f (readdir $dir) { + $f =~ /(\.rpm|^hdlist.cz)$/ or next; + if (!link "$local_media/$f", "$chroot_tmp/iurt_media") { + if (!copy "$local_media/$f", "$chroot_tmp/iurt_media") { + plog("ERROR: could not copy file $local_media/$f to $chroot_tmp/iurt_media"); + $next = 1; + last; + } + } + } + next if $next; + add_media($self, $chroot_tmp, 'iurt_group', "iurt_group file:///iurt_media") or next; + } + + $self->{urpmi_command} = "chroot $chroot_tmp urpmi $self->{urpmi_options} "; + return 1; + } else { + $self->{urpmi_command} = "urpmi $self->{urpmi_options} $self->{urpmi_media_options} --root $chroot_tmp"; + } +} + +sub check_media_added { + my ($chroot, $media) = @_; + my $medias = `sudo chroot $chroot urpmq --list-media 2>&1`; + print "MEDIA $medias ($media)\n"; + $medias =~ /$media/m; +} + +sub add_media { + my ($self, $chroot, $regexp, $media) = @_; + my $run = $self->{run}; + my $config = $self->{config}; + my $cache = $run->{cache}; + + plog("add chroot media: $run->{chrooted_media}"); + + if (!perform_command("sudo chroot $chroot urpmi.addmedia --probe-hdlist $media", + $run, $config, $cache, + mail => $config->{admin}, + timeout => 300, + freq => 1, + retry => 2, + debug_mail => $run->{debug})) { + } + if (!check_media_added($chroot, $regexp)) { + plog('ERR', "ERROR iurt could not add media into the chroot"); + return; + } + 1; +} + +sub add_packages { + my ($self, $chroot, $_user, @packages) = @_; + my $run = $self->{run}; + my $config = $self->{config}; + my $cache = $run->{cache}; + if (!perform_command("sudo $self->{urpmi_command} @packages", + $run, $config, $cache, + timeout => 300, + freq => 1, + retry => 2, + error_ok => [ 11 ], + debug_mail => $run->{debug}, + error_regexp => 'cannot be installed', + wait_regexp => { + 'is needed by' => sub { + plog("WARNING: rpm database seems corrupted, retrying"); + system("sudo chroot $chroot rm -rf /var/lib/rpm/__db* &> /dev/null"); + 1; + }, + 'database locked' => sub { + plog("WARNING: urpmi database locked, waiting..."); + sleep 30; + $self->{wait_limit}++; + if ($self->{wait_limit} > 10) { + $self->{wait_limit} = 0; + system(qq(sudo pkill -9 urpmi &>/dev/null)); + return; + } + 1; + } },)) { + plog("ERROR: could not install @packages inside $chroot"); + return 0; + } + 1; +} + +sub get_local_provides { + my ($self) = @_; + my $run = $self->{run}; + my $program_name = $run->{program_name}; + my $local_media = $self->{local_media}; + + opendir my $dir, $local_media; + plog(1, "get local provides ($local_media)"); + require URPM; + my $urpm = new URPM; + foreach my $d (readdir $dir) { + $d =~ /\.src\.rpm$/ and next; + $d =~ /\.rpm$/ or next; + my $id = $urpm->parse_rpm("$local_media/$d"); + my $pkg = $urpm->{depslist}[$id]; + plog(3, "$program_name: checking $d provides"); + foreach ($pkg->provides, $pkg->files) { + plog(3, "$program_name: adding $_ as provides of $d"); + $run->{local_provides}{$_} = $d; + } + } + 1; +} + +sub get_build_requires { + my ($self, $union_id, $luser) = @_; + my $run = $self->{run}; + my $config = $self->{config}; + my $cache = $run->{cache}; + + $run->{todo_requires} = {}; + plog("get_build_requires"); + + my ($u_id, $chroot_tmp) = create_temp_chroot($run, $config, $cache, $union_id, $run->{chroot_tmp}, $run->{chroot_tar}) or return; + add_local_user($chroot_tmp, $run, $config, $luser, $run->{uid}) or return; + $union_id = $u_id; + + my $urpm = new URPM; + foreach my $p (@{$run->{todo}}) { + my ($dir, $srpm, $s) = @$p; + recreate_srpm($self, $run, $config, $chroot_tmp, $dir, $srpm, $run->{user}) or return; + $s or next; + my $id = $urpm->parse_rpm("$dir/$srpm"); + my $pkg = $urpm->{depslist}[$id]; + foreach ($pkg->requires) { + plog(3, "adding $_ as requires of $srpm"); + $run->{todo_requires}{$_} = $srpm; + } + } + 1; +} + +sub order_packages { + my ($self, $union_id, $provides, $luser) = @_; + my $run = $self->{run}; + my @packages = @{$run->{todo}}; + my $move; + + plog(1, "order_packages"); + get_local_provides($self) or return; + if (!$run->{todo_requires}) { + get_build_requires($self, $union_id, $luser) or return; + } + my %visit; + my %status; + do { + $move = 0; + foreach my $p (@packages) { + my ($_dir, $rpm, $status) = @$p; + defined $status{$rpm} && $status{$rpm} == 0 and next; + plog("checking packages $rpm"); + foreach my $r (@{$run->{todo_requires}{$rpm}}) { + plog("checking requires $r"); + if (!$run->{local_provides}{$r}) { + if ($provides->{$r}) { + $status = 1; + } else { + $status = 0; + } + } elsif ($visit{$rpm}{$r}) { + # to evit loops + $status = 0; + } elsif ($run->{done}{$rpm} && $run->{done}{$provides->{$r}}) { + if ($run->{done}{$rpm} < $run->{done}{$provides->{$r}}) { + $move = 1; + $status = $status{$provides->{$r}} + 1; + } else { + $status = 0; + } + } elsif ($status < $status{$provides->{$r}}) { + $move = 1; + $status = $status{$provides->{$r}} + 1; + } + $visit{$rpm}{$r} = 1; + } + $status{$rpm} = $status; + $p->[2] = $status; + } + } while $move; + $run->{todo} = [ sort { $a->[2] <=> $b->[2] } @packages ]; + if ($run->{verbose}) { + foreach (@packages) { + plog("order_packages $_->[1]"); + } + } + @packages; +} + +sub wait_urpmi { + my ($self) = @_; + my $run = $self->{run}; + + plog("WARNING: urpmi database locked, waiting...") if $run->{debug}; + sleep 30; + $self->{wait_limit}++; + if ($self->{wait_limit} > 8) { + $self->{wait_limit} = 0; system(qq(sudo pkill -9 urpmi &>/dev/null)); + } +} + +sub install_packages_old { + my ($self, $local_spool, $srpm, $log, $error, @packages) = @_; + my $run = $self->{run}; + my $config = $self->{config}; + my $cache = $run->{cache}; + my $log_spool = "$local_spool/log/$srpm/"; + -d $log_spool or mkdir $log_spool; + if (!perform_command("sudo $self->{urpmi_command} @packages", + $run, $config, $cache, + # mail => $maintainer, + error => $error, + hash => "${log}_$srpm", + srpm => $srpm, + timeout => 600, + retry => 2, + debug_mail => $run->{debug}, + freq => 1, + wait_regexp => { 'database locked' => \&wait_urpmi }, + error_regexp => 'unable to access', + log => $log_spool)) { + $cache->{failure}{$srpm} = 1; + $run->{status}{$srpm} = 'binary_test_failure'; + return 0; + } + 1; +} + +sub install_packages { + my ($self, $title, $chroot_tmp, $local_spool, $pack_provide, $log, $error, $opt, @packages) = @_; + + my $maintainer = $opt->{maintainer}; + my $run = $self->{run}; + my $config = $self->{config}; + my $cache = $run->{cache}; + my $program_name = $run->{program_name}; + my $ok; + my @to_install; + + plog('DEBUG', "installing @packages"); + + if ($run->{chrooted_urpmi}) { + @to_install = map { s/$chroot_tmp//; $_ } @packages; + } else { + push @to_install, @packages; + } + + @to_install or return 1; + + (my $log_dirname = $title) =~ s/.*:(.*)\.src.rpm/$1/; + + my $log_spool = "$local_spool/log/$log_dirname/"; + + mkdir $log_spool; + + my $try_urpmi = 1; + my @rpm = grep { !/\.src\.rpm$/ } @to_install; + + return 1 if ($opt->{check} && -f "$chroot_tmp/bin/rpm" && @rpm && !system("sudo chroot $chroot_tmp rpm -q @to_install")); + + if ($try_urpmi) { + foreach my $try ( + [ '', 'using urpmi' ], + [ '', 'rebuild rpm base and retry', '_retry' ], + [ ' --allow-nodeps', 'retrying with nodeps' , '_nodeps' ], + [ ' --no-install', 'using rpm directly', '_rpm' ] + ) { + my ($opt, $msg, $suf) = @$try; + + plog('INFO', "install dependencies: $msg"); + my $unsatisfied; + + if (!perform_command( + "sudo $self->{urpmi_command} $opt @to_install", + $run, $config, $cache, + error => $error, + logname => "${log}$suf", + hash => "${log}_$title$suf", + timeout => 600, + srpm => $title, + freq => 1, + #cc => $cc, + retry => 3, + debug_mail => $run->{debug}, + error_regexp => 'cannot be installed', + wait_regexp => { + 'database locked' => \&wait_urpmi, + 'is needed by' => sub { + plog('WARN', "WARNING: rpm database seems corrupted, retrying"); + system("sudo chroot $chroot_tmp rm -rf /var/lib/rpm/__db* &> /dev/null"); + 1; + }, + }, + log => $log_spool, + callback => sub { + my ($opt, $output) = @_; + plog('DEBUG', "calling callback for $opt->{hash}"); + +# 20060614 +# it seems the is needed urpmi error is due to something else (likely a +# database corruption error). +# my @missing_deps = $output =~ /(?:(\S+) is needed by )|(?:\(due to unsatisfied ([^[ ]*)(?: (.*)|\[(.*)\])?\))/g; +# + + my @missing_deps = $output =~ /([^ \n]+) \(due to unsatisfied ([^[ \n]*)(?: ([^\n]*)|\[([^\n]*)\])?\)/g; + + # as it seems that rpm db corruption is making urpmi + # returning false problem on deps installation, try + # to compile anyway + + @missing_deps or return 1; + $unsatisfied = 1; + + while (my $missing_package = shift @missing_deps) { + my $missing_deps = shift @missing_deps; + my $version = shift @missing_deps; + my $version2 = shift @missing_deps; + $version ||= $version2 || 0; + my $p = $pack_provide->{$missing_deps} || $missing_deps; + my ($missing_package_name, $first_maint); + if ($missing_package !~ /\.src$/) { + ($first_maint, $missing_package_name) = get_maint($run, $missing_package); + plog(5, "likely $missing_package_name need to be rebuilt ($first_maint)"); + } else { + $missing_package = ''; + } + + my ($other_maint) = get_maint($run, $p); + plog('FAIL', "missing dep: $missing_deps ($other_maint) missing_package $missing_package ($first_maint)"); + $run->{status}{$title} = 'missing_dep'; + foreach my $m ($first_maint, $other_maint) { # FIXME: (tv) this loop is useless !!! + if ($other_maint && $other_maint ne 'NOT_FOUND') { + $opt->{mail} = $config->{admin}; + #$opt->{mail} .= ", $other_maint"; + } + } + + if (!$opt->{mail}) { + $opt->{mail} = $config->{admin}; + } + + # remember what is needed, and do not try to + # recompile until it is available + + if ($missing_package) { + $opt->{error} = "[MISSING] $missing_deps, needed by $missing_package to build $title, is not available on $run->{my_arch} (rebuild $missing_package?)"; + $cache->{needed}{$title}{$missing_deps} = { package => $missing_package , version => $version, maint => $first_maint || $other_maint || $maintainer }; + } else { + $opt->{error} = "[MISSING] $missing_deps, needed to build $title, is not available on $run->{my_arch}"; + $cache->{needed}{$title}{$missing_deps} = { package => $missing_package , version => $version, maint => $maintainer || $other_maint }; + } + } + 0; + }, + )) { + if (!clean_process($run, "$self->{urpmi_command} $opt @to_install", $run->{verbose})) { + dump_cache_par($run); + die "FATAL $program_name: Could not have urpmi working !"; + } + $unsatisfied and last; + } else { + if (!@rpm || !system("sudo chroot $chroot_tmp rpm -q @rpm")) { + plog("installation successful"); + $ok = 1; + } + } + + if (-f "$chroot_tmp/bin/rpm") { + if (!$ok && system("sudo chroot $chroot_tmp rm -rf /var/lib/rpm/__db*; sudo chroot $chroot_tmp rpm --rebuilddb")) { + plog("ERROR: rebuilding rpm db failed, aborting ($!)"); + last; + } + if ($suf eq '_rpm') { + plog(1, "trying to install all the rpms in $chroot_tmp/var/cache/urpmi/rpms/ manually"); + if (!system("sudo chroot $chroot_tmp rpm -Uvh --force --nodeps /var/cache/urpmi/rpms/*.rpm")) { + $ok = 1; + last; + } else { + $ok = 0; + } + } + } + last if $ok == 1; + } + } + if (!-f "$chroot_tmp/bin/rpm" || @rpm && system("sudo chroot $chroot_tmp rpm -q @to_install")) { + plog(1, "ERROR: urpmi is not working, doing it manually"); + my $root = "$config->{repository}/$run->{distro}/$run->{my_arch}"; + my $depslist = "$root/media/media_info/depslist.ordered"; + if (-f $depslist) { + my $distrib = $self->{distrib}; + if (!$distrib) { + $distrib = MDV::Distribconf::Build->new($root); + plog(3, "getting media config from $root"); + if (!$distrib->loadtree) { + plog(1, "ERROR: $root does not seem to be a distribution tree\n"); + return; + } + $distrib->parse_mediacfg; + foreach my $media ($distrib->listmedia) { + $media =~ /(SRPMS|debug_)/ and next; + my $path = $distrib->getfullpath($media, 'path'); + opendir my $rpmdh, $path; + foreach my $rpm (readdir $rpmdh) { + if ($rpm =~ /^(.*)-([^-]+)-([^-]+)\.([^.]+)\.rpm/) { + $distrib->{file}{"$1-$2-$3.$4"} = "$path/$rpm"; + } + } + } + } + $self->{distrib} = $distrib; + plog(3, "using $depslist to resolve dependencies"); + open my $depsfh, $depslist; + my %packages; + my @deps; + my $i; + my @install; + my @pack; + while (<$depsfh>) { + my ($pack, $_size, @d) = split ' '; + $pack =~ s/:\d+$//; + push @deps, \@d; + my ($name, $version, $release, $arch) = $pack =~ /(.*)-([^-]+)-([^-]+)\.([^.]+)$/; + $pack[$i] = $pack; + $packages{$pack} ||= $i; + if ($arch ne 'src' && !$packages{$name}) { + $packages{$name} = $i; + $packages{"$name-$version"} = $i; + $packages{"$name-$version-$release"} = $i; + } + $i++; + } + plog(4, "$i packages found"); + my %done; + # rpm -root $chroot -qa does not work + if (-f "$chroot_tmp/bin/rpm") { + my $qa = `sudo chroot $chroot_tmp rpm -qa --qf "\%{name}-\%{version}-\%{release}.\%{arch}\n"`; + foreach my $rpm (split "\n", $qa) { + plog(6, "$rpm already installed"); + $done{$rpm} = 1; + } + } + foreach my $p (@to_install) { + my $pa = $pack[$packages{$p}]; + $done{$pa} and next; + my $f = $distrib->{file}{$pa}; + if ($f) { + push @install, $pa; + } else { + plog(1, "ERROR: main package $p is not present in the repository"); + return 0; + } + } + my $dok; + while (!$dok) { + $dok = 1; + foreach my $p (@install) { + $done{$p} and next; + $done{$p} = 1; + plog(5, "adding deps for $p (", join(', ', @{$deps[$packages{$p}]}, ")")); + foreach my $d (@{$deps[$packages{$p}]}) { + plog("$d (pack $pack[$d]) done $done{$d} done pack $done{$pack[$d]}"); + $done{$d} and next; + $done{$pack[$d]} and next; + $done{$d} = 1; + if ($d =~ /\d+/) { + my $f = $distrib->{file}{$pack[$d]}; + if ($f) { + $dok = 1; + plog(5, "adding $pack[$d]"); + push @install, $pack[$d]; + } else { + plog(2, "ERROR: deps for $p, $pack[$d] ($d) is not present in the repository"); + } + } elsif ($d =~ /\|/) { + my $done; + foreach my $a (split '\|', $d) { + my $f = $distrib->{file}{$pack[$a]}; + if ($f) { + $done = 1; + if (!$done{$pack[$a]}) { + $dok = 1; + plog(5, "adding $pack[$a]"); + push @install, $pack[$a]; + $done{$pack[$a]} = 1; + } + last; + } else { + plog(2, "ERROR: alternate deps, $pack[$a] ($d) is not present in the repository, using alternative"); + } + } + if (!$done) { + plog(2, "ERROR: no alternatives present in the repository"); + } + } + } + } + } + my $rpms; + my %install_done; + # FIXME: (tv) this loop could be simplified with uniq() from MDK::Common: + # eg: $rpms = join(map { "$distrib->{file}{$_}" } uniq(@install)) + foreach my $rpm (@install) { + $install_done{$rpm} and next; + print "$program_name: will install $rpm ($distrib->{file}{$rpm})\n" if $run->{verbose} > 3; + $install_done{$rpm} = 1; + $rpms .= "$distrib->{file}{$rpm} "; + } + if ($rpms) { + return !system("sudo rpm --ignoresize --nosignature --root $chroot_tmp -Uvh $rpms"); + } + $ok = 1; + } + } + $ok; +} + +sub clean_urpmi_process { + my ($self) = @_; + my $run = $self->{run}; + my $program_name = $run->{program_name}; + if (!$run->{chrooted_urpmi}) { + my $match = $self->{urpmi_command} or return; + if (!clean_process($run, $match, $run->{verbose})) { + dump_cache_par($run); + die "FATAL $program_name: Could not have urpmi working !"; + } + } +} + +sub update_srpm { + my ($self, $dir, $rpm, $wrong_rpm) = @_; + my $run = $self->{run}; + my $cache = $run->{cache}; + my ($arch) = $rpm =~ /([^\.]+)\.rpm$/ or return 0; + my $srpm = $cache->{rpm_srpm}{$rpm}; + if (!$srpm) { + my $hdr = RPM4::Header->new("$dir/$rpm"); + $hdr or return 0; + $srpm = $hdr->queryformat('%{SOURCERPM}'); + $cache->{rpm_srpm}{$rpm} = $srpm; + } + $srpm = fix_srpm_name($cache, $srpm, $rpm, $wrong_rpm); + $arch, $srpm; +} + +sub fix_srpm_name { + my ($cache, $srpm, $rpm, $wrong_rpm) = @_; + my $old_srpm = $srpm; + if ($srpm =~ s/^lib64/lib/) { + push @$wrong_rpm, [ $old_srpm, $rpm ] if ref $wrong_rpm; + $cache->{rpm_srpm}{$rpm} = $srpm; + } + $srpm; +} + +sub recreate_srpm { + my ($_self, $run, $config, $chroot_tmp, $dir, $srpm, $luser, $b_retry) = @_; +# recreate a new srpm for buildarch condition in the spec file + my $program_name = $run->{program_name}; + my $cache = $run->{cache}; + my $with_flags = $run->{with_flags}; + + plog('NOTIFY', "recreate srpm: $srpm"); + + perform_command([ + sub { + my ($s, $d) = @_; + sudo($run, $config, '--cp', $s, $d) } , [ "$dir/$srpm", "$chroot_tmp/home/$luser/rpm/SRPMS/" ] ], + $run, $config, $cache, + type => 'perl', + mail => $config->{admin}, + error => "[REBUILD] cannot copy $srpm to $chroot_tmp", + debug_mail => $run->{debug}, + hash => "copy_$srpm") or return; + + my %opt = (mail => $config->{admin}, + error => "[REBUILD] cannot install $srpm in $chroot_tmp", + debug_mail => $run->{debug}, + hash => "install_$srpm", + retry => $b_retry, + callback => sub { + my ($opt, $output) = @_; + plog('DEBUG', "calling callback for $opt->{hash}"); + if ($output =~ /warning: (group|user) .* does not exist - using root|Header V3 DSA signature/i) { + return 1; + } elsif ($output =~ /user $luser does not exist|cannot write to \%sourcedir/) { + plog('WARN', "WARNING: chroot seems corrupted!"); + $opt->{error} = "[CHROOT] chroot is corrupted"; + $opt->{retry} ||= 1; + return; + } + 1; + }); + plog('DEBUG', "recreating src.rpm..."); + if (!perform_command(qq(sudo chroot $chroot_tmp su $luser -c "rpm -i /home/$luser/rpm/SRPMS/$srpm"), + $run, $config, $cache, %opt)) { + plog("ERROR: chrooting failed (retry $opt{retry}") if $run->{debug}; + if ($opt{retry}) { + check_build_chroot($run->{chroot_path}, $run->{chroot_tar}, $run, $config) or return; + return -1; + } + return; + } + + my $spec; + my $oldsrpm = "$chroot_tmp/home/$luser/rpm/SRPMS/$srpm"; + my $filelist = `rpm -qlp $oldsrpm`; + my ($name) = $srpm =~ /(?:.*:)?(.*)-[^-]+-[^-]+\.src\.rpm$/; + foreach my $file (split "\n", $filelist) { + if ($file =~ /(.*)\.spec/) { + if (!$spec) { + $spec = $file; + } elsif ($1 eq $name) { + $spec = $file; + } + } + } + # 20060515 This should not be necessairy any more if urpmi *.spec works, but it doesn't + # + my $ret = perform_command(qq(sudo chroot $chroot_tmp su $luser -c "rpm --nodeps -bs $with_flags /home/$luser/rpm/SPECS/$spec"), + $run, $config, $cache, + mail => $config->{admin}, + error => "[REBUILD] cannot create $srpm in $chroot_tmp", + debug_mail => $run->{debug}, + hash => "create_$srpm" + ); + + # Return if we can't regenerate srpm + # + return (0, ,) unless $ret; + + # CM: was: foreach my $file (readdir $dir) + # The above line returned entries in a strange order in my test + # system, such as + # .. + # cowsay-3.03-11mdv2007.1.src.rpm + # cowsay-3.03-11mdv2007.0.src.rpm + # . + # assigning '.' to $new_rpm. Now sorting the output. + + # we should better perform a rpm -qp -qf "%{name}-%{version}-%{release}.src.rpm" $spec + my $file; + my $stat; + foreach my $f (glob "$chroot_tmp/home/$luser/rpm/SRPMS/$name-*.src.rpm") { + my (@s) = stat $f; + if ($s[9] > $stat) { + $file = $f; + $stat = $s[9]; + } + } + my ($new_srpm) = basename($file); + my $prefix = get_package_prefix($srpm); + my $newfile = "$chroot_tmp/home/$luser/rpm/SRPMS/$prefix$new_srpm"; + if (-f $file && $newfile ne $file) { + if (-f $newfile) { + sudo($run, $config, '--rm', $newfile) or die "$program_name: could not delete $newfile ($!)"; + } + sudo($run, $config, '--ln', $file, $newfile) or die "$program_name: linking $file to $newfile failed ($!)"; + unlink $file; + unlink $oldsrpm if $oldsrpm ne $newfile; + } + plog('NOTIFY', "new srpm: $prefix$new_srpm"); + ($ret, "$prefix$new_srpm", $spec); +} + +1; diff --git a/lib/Iurt/Util.pm b/lib/Iurt/Util.pm new file mode 100644 index 0000000..a101f76 --- /dev/null +++ b/lib/Iurt/Util.pm @@ -0,0 +1,203 @@ +package Iurt::Util; + +use base qw(Exporter); +use strict; + +our @EXPORT = qw( + plog_init + plog + pdie + ssh_setup + ssh + sout + sget + sput +); + +my ($plog_name, $plog_file, $plog_level, $plog_color); + +=head2 LOG HELPERS + +=over 8 + +=item plog_init($program_name, $logfile) + +=item plog_init($program_name, $logfile, $level) + +Initialize plog with the program name, log file and optional log level. +If not specified, the log level will be set to 9999. + +=cut + +my %plog_ctr = ( + red => "\x1b[31m", + green => "\x1b[32m", + yellow => "\x1b[33m", + blue => "\x1b[34m", + magenta => "\x1b[35m", + cyan => "\x1b[36m", + grey => "\x1b[37m", + bold => "\x1b[1m", + normal => "\x1b[0m", +); + +my @plog_prefix = ( + "", + "E: ", + "W: ", + "*: ", + "F: ", + "O: ", + "N: ", + "I: ", + "D: ", +); + +my %plog_level = ( + NONE => 0, + ERR => 1, + WARN => 2, + MSG => 3, + FAIL => 4, + OK => 5, + NOTIFY => 6, + INFO => 7, + DEBUG => 8, +); + +sub plog_init { + $plog_name = shift; + $plog_file = shift; + $plog_level = shift @_ || 9999; + $plog_color = shift @_ || 0; + + $plog_level = 9999 if $ENV{PLOG_DEBUG}; + + $plog_color = 0 unless -t fileno $plog_file; + + foreach (@plog_prefix) { $_ .= "[$plog_name] " } + + if ($plog_color) { + $plog_prefix[1] .= "$plog_ctr{bold}$plog_ctr{red}"; + $plog_prefix[2] .= "$plog_ctr{bold}$plog_ctr{yellow}"; + $plog_prefix[3] .= $plog_ctr{bold}; + $plog_prefix[4] .= $plog_ctr{red}; + $plog_prefix[5] .= $plog_ctr{green}; + $plog_prefix[6] .= $plog_ctr{cyan}; + $plog_prefix[8] .= $plog_ctr{yellow}; + } + + 1; +} + +=item plog($message) + +=item plog($level, @message) + +Print a log message in the format "program: I<message>\n" to the log +file specified in a call to plog_init(). If a level is specified, +the message will be printed only if the level is greater or equal the +level set with plog_init(). + +=back + +=cut + +sub plog { + my $level = $#_ ? shift : 'INFO'; + $level = $plog_level{$level}; + my ($p, $e) = ($plog_prefix[$level], $plog_ctr{normal}); + + print $plog_file "$p@_$e\n" if $plog_level >= $level; +} + +sub pdie { + my $level = $plog_level{ERROR}; + my ($p, $e) = ($plog_prefix[$level], $plog_ctr{normal}); + + print $plog_file "$p@_$e\n" if $plog_level >= $level; + die $@; +} + +=head2 SSH HELPERS + +=over 8 + +=item ssh_setup($options, $user, $host) + +Set up ssh connections with the specified options, user and remote +host. Return an ssh handle to be used in ssh-based operations. + +=cut + +sub ssh_setup { + my $opt = shift; + my $user = shift; + my $host = shift; + my @conf = ($opt, $user, $host); + \@conf; +} + +=item ssh($handle, @commmand) + +Open an ssh connection with parameters specified in ssh_setup() and +execute I<@command>. Return the command execution status. + +=cut + +# This is currently implemented using direct calls to ssh/scp because. +# according to Warly's comments in ulri, using the perl SSH module +# gives us some performance problems + +sub ssh { + my $conf = shift; + my ($opt, $user, $host) = @$conf; + system("ssh $opt -x $user\@$host @_"); +} + +=item sout($handle, @commmand) + +Open an ssh connection with parameters specified in ssh_setup() and +execute I<@command>. Return the command output. + +=cut + +sub sout { + my $conf = shift; + my ($opt, $user, $host) = @$conf; + `ssh $opt -x $user\@$host @_ 2>/dev/null`; +} + +=item sget($handle, $from, $to) + +Get a file using scp, from the remote location I<$from> to the +local location I<$to>, using host and user specified in ssh_setup(). + +=cut + +sub sget { + my $conf = shift; + my ($_opt, $user, $host) = @$conf; + system('scp', '-q', '-rc', 'arcfour', "$user\@$host:$_[0]", $_[1]); +} + +=item sput($handle, $from, $to) + +Send a file using scp, from a local location I<$from> to the remote +location I<$to>, using host and user specified in ssh_setup(). + +=back + +=cut + +sub sput { + my $conf = shift; + my ($_opt, $user, $host) = @$conf; + system('scp', '-q', '-rc', 'arcfour', $_[0], "$user\@$host:$_[1]"); +} + +=back + +=cut + +1; @@ -0,0 +1,1497 @@ +#!/usr/bin/perl +# +# Copyright (C) 2005 Mandrakesoft +# Copyright (C) 2005,2006 Mandriva +# +# Author: Florent Villard <warly@mandriva.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# NAME +# +# uiurt - micro iurt +# +# DESCRIPTION +# +# Minimal, strict, functionality to build packages in a chroot. +# +# Does not try to be smart and resolve host environment problems, but just +# fail and report insted. +# +# TODO +# +# - use a cache (rpmctl cache for example) to find maintainer +# - add icecream compilation support +# - add a --group option to compile a set of packages (in progress) +# - add a function to update a packages when it obviously need to be recompile +# - Maybe call the function from the initial todo list (thus making the +# argument ordering important) +# - Change the packager tag in the chroot to have the one who submit the package + +use strict; +use RPM4::Header; +use Iurt::Config qw(config_usage get_date get_prefix config_init dump_cache_par get_maint get_date check_arch %arch_comp get_package_prefix); +use Data::Dumper; +use URPM; +use Iurt::DKMS; +use Iurt::Urpmi; +use Iurt::Chroot qw(add_local_user create_temp_chroot remove_chroot clean_unionfs clean_all_unionfs clean_all_chroot_tmp check_build_chroot clean_chroot); +use Iurt::Process qw(perform_command clean kill_for_good sudo); +use Iurt::Mail qw(sendmail); +use Iurt::Util qw(plog_init plog); +use File::NCopy qw(copy); +use File::Path qw(mkpath); +use File::Spec::Functions qw(rel2abs); +use File::Basename qw(fileparse); +# I did not manage to make locks work over the network +#use File::lockf; +use Mkcd::Commandline qw(parseCommandLine usage); +use MDK::Common; +use Filesys::Df qw(df); + +my $program_name = 'iurt2'; +my $VERSION = '0.6.2'; +# sessing parameters +my $sudo = '/usr/bin/sudo'; +my $arg = @ARGV; +my (@params, %run); +$run{program_name} = $program_name; + +$run{todo} = []; +@params = ( + # [ "one letter option", "long name option", "number of args (-X means ´at least X´)", "help text", "function to call", "log info"] + # + # no_rsync, config_help and copy_srpm kept for compatibility reasons + # + [ "", $program_name, 0, "[--cache] [--chrooted-urpmi <media prefix>] [--concurrent-run] [--config foo value] [--warn] [--verbose integer] + [--copy-srpm] [--debug] [--distro] [--no-rsync] [--clean user1 user2 user3] [--clean-all] [--shell] [--stop {p|c|i|l|b|a|s}] + [--use-system-distrib] [--dir] [--help foo?] [--log filename] [--group] [--unionfs] + [--upload [--markrelease] [--source]] [--dir] [--help foo?] [--log filename] [--unionfs] [--status] [--ignore-failure] + [--repository <distribution path>] + {--config_help | --dkms {--media <media regexp>} + --chroot --arch {i586|x86_64|ppc} --distro {cooker|2006.0|community/2006.0|...} } | + --rebuild {cooker|2006.0|community/2006.0|...} {i586|x86_64|ppc|...} {filename1.src.rpm} {filename2.src.rpm} ... {filenamen.src.rpm} }", + "$program_name is a perl script to rebuild automatically several rpm in chroot, given a sourcerpm repository, and mail authors or rebuilder when problems occurs. + + e.g.: iurt --repository /dis/ -p foo\@foo.net -r cooker x86_64 /SRPMS/main/release/mkcd-4.2.5-1mdv2007.1.src.rpm", + sub { $arg or usage($program_name, \@params) }, "" ], + [ "", "distro", 1, "<distro>", + "Set the distribution", + sub { ($run{distro}) = @_; 1 }, "Setting the distribution" ], + [ "", "dkms", [ + ["", "dkms", 0, "", + "Set the DKMS rebuild mode", + sub { + my ($tmp, @arg) = @_; + $tmp->[0] ||= {}; + push @$tmp, @arg; + 1; + }, "Setting auto mode arguments"], + ["k", "kmedia", 1, "<kernel media regexp>", + "Media Regexp to limit the kernel search to", + sub { my ($tmp, $kmedia) = @_; $tmp->[0]{kmedia} = $kmedia; 1 }, "Limiting rebuild to the kernel in the given media regexp"], + ["m", "media", 1, "<media regexp>", + "Media Regexp to limit rebuild to", + sub { my ($tmp, $media) = @_; $tmp->[0]{media} = $media; 1 }, "Limiting rebuild to the given media regexp"], +], "[options]", + "Set the DKMS rebuild mode", + sub { my ($opt) = @_; $run{dkms} = $opt; 1 }, "Running a DKMS rebuild run" ], + [ "a", "arch", 1, "<architecture>", + "Set the architecture", + sub { ($run{my_arch}) = @_; 1 }, "Setting architecture" ], + [ "", "cache", 0, "", + "Use the global cache file", + sub { $run{use_cache} = 1 }, "Activating cache use" ], + [ "", "copy-srpm", 0, "", + "Copy also the regenerated SRPM", + sub { $run{copy_srpm} = 1 }, "Activating the copy_srpm mode" ], + [ "", "copy_srpm", 0, "", + "Copy also the regenerated SRPM", + sub { $run{copy_srpm} = 1 }, "Activating the copy_srpm mode" ], + [ "c", "chroot", 0, "", + "Check chroot and update it if needed", + sub { $run{chroot} = 1 }, "Activating chroot updating" ], + [ "", "chrooted-urpmi", [ + [ "", "chrooted-urpmi", 1, "", + "Create urpmi media inside the chroot instead of using --root (media prefix is like http:///server.mandriva.com/dis/)", + sub { + my ($tmp, @arg) = @_; + $tmp->[0] ||= {}; + push @$tmp, @arg; + 1; + }, "Setting chrooted-urpmi options" ], + ["m", "media", -1, "<media1> <media2> ... <median>", + "Media to add instead of --distrib", + sub { my ($tmp, @media) = @_; $tmp->[0]{media} = \@media; 1 }, "Limiting rebuild to the kernel in the given media regexp"], + ] , "[options] <media prefix>", + "Create urpmi media inside the chroot instead of using --root (media prefix is like http:///server.mandriva.com/dis/)", + sub { my ($opt, $media) = @_; $opt->{rooted_media} = $media; $run{chrooted_urpmi} = $opt; 1 }, "Activating chroot media" ], + [ "", "clean-all", 0, "", + "Clean all remaining chroots for all the users", + sub { $run{clean_all} = 1 }, "Activating clean chroot flag" ], + [ "", "clean", -1, "<user 1> <user 2> ... <user n>", + "Clean remaining chroot before runing", + sub { $run{clean} = \@_ }, "Activating clean chroot flag" ], + [ "", "concurrent-run", 0, "", + "Allow several iurt to run on different machines (slower)", + sub { $run{concurrent_run} = 1 }, "Activating concurrent run checks" ], + [ "d", "dir", -1, "", + "Directory where to find packages to rebuild", + sub { $run{extra_dir} = \@_; 1 }, "Adding extra source packages directories" ], + [ "", "config", 2, "<configuration keyword> <value>", + "Override a configuration file variable", + sub { my ($key, $value) = @_; $run{config}{$key} = $value }, "Overriding configuration variable" ], + [ "", "config-help", 0, "", + "Explain configuration files keywords", + sub { $run{config_usage} = 1 }, "Activating debug mode" ], + [ "", "config_help", 0, "", + "Explain configuration files keywords", + sub { $run{config_usage} = 1 }, "Activating debug mode" ], + [ "", "debug", 0, "", + "Activate debug mode", + sub { $run{debug} = 1 }, "Activating debug mode" ], + [ "g", "group", 0, "", + "Activate group mode, packages will be compiled as a global set, not as individual packages", + sub { $run{group} = 1 }, "Activating the group mode" ], + [ "", "ignore-failure", 0, "", + "Do not take into account the failure cache, try to recompile all the packages not synchronized", + sub { $run{ignore_failure} = 1 }, "Activating the mode ignoring previous failure" ], + [ "u", "unionfs", 0, "", + "Activate unionfs mode", + sub { $run{unionfs} = 1 }, "Activating unionfs mode" ], + [ "l", "log", 1, "<log file>", + "Log file.", + sub { + $run{log} = pop @_; + open my $log, ">$run{log}" or die "unable to open $run{log}\n"; + $run{LOG} = sub { print $log @_ }; + print *$log, "command line: @ARGV\n"; + 1; + }, "Log file" ], + [ "m", "media", -1, "<media 1> <media 2> ... <media 3>", + "Media to rebuild", + sub { ($run{media}) = @_; 1 }, "Adding a media to rebuild" ], + [ "n", "no", 0, "", + "Perform all the check but do not compile anything", + sub { ($run{no_compile}) = 1 }, "Setting the no compilation flag" ], + [ "p", "packager", 1, "<packager>", + "Use a specific packager", + sub { ($run{packager}) = @_ }, 'Setting packager tag'], + [ "r", "rebuild", -2, "<distro> <architecture> <srpm 1> <srpm 2> ... <srpm n>", + "Rebuild the packages, e.g. $program_name -r cooker x86_64 /home/foo/rpm/SRPMS/foo-2.3-12mdv2007.0.src.rpm", + sub { + $run{rebuild} = 1; + $run{distro} = shift @_; + $run{my_arch} = shift @_; + + foreach (@_) { + my ($path, $srpm); + + unless (-f $_ && -r $_) { + die "FATAL $program_name: $_ not a file or cannot be read\n"; + } + + ($srpm, $path) = fileparse(rel2abs($_)); + ($srpm =~ /\.src\.rpm$/) || die "FATAL: $_ doesn't look like an SRPM"; + + if (check_arch($_, $run{my_arch})) { + plog('DEBUG', "force build for $2 (from $1)"); + push @{$run{todo}}, [ $path, $srpm, 1 ]; + } else { + plog("ERROR: $_ could not be build on $run{my_arch}, ignored."); + } + } + 1; + }, "Activating rebuild mode" ], + [ "", "upload", [ + ["", "upload", 0, "[options]", + "Upload the rebuild packages", + sub { my ($tmp) = @_; + $tmp->[0] ||= {}; + 1; + }, "Setting upload options"], + [ "m", "markrelease", 0, "", + "Mark SVN directory when uploading the packages", + sub { $run{markrelease} = 1 }, "Adding markrelease repsys option" ], + [ "s", "source", 0, "", + "Upload the source package as wells", + sub { $run{source_upload} = 1 }, "Setting source flag for upload" ], + ], "[options]", + "Upload the rebuild packages", + sub { $run{upload} = 1 }, "Setting the upload flag" ], + [ "", "use-old-chroot", 1, "<chroot path>", + "Use the given chroot as chroot (usefull for debugging)", + sub { ($run{use_old_chroot}) = @_ }, "Using given chroot" ], + [ "", "no_rsync", 0, "", + "Do not send build log to the distant rsync server", + sub { $run{no_rsync} = 1 }, "Setting the no rsync warn flag" ], + [ "", "no-rsync", 0, "", + "Do not send build log to the distant rsync server", + sub { $run{no_rsync} = 1 }, "Setting the no rsync warn flag" ], + [ "", "use-system-distrib", 1, "<media>", + "Use the current system urpmi configuration", + sub { $run{use_system_distrib} = shift; 1 }, "Setting system distrib for urpmi configuration" ], + [ "v", "verbose", 1, "<verbose level>", + "Give more info messages about what is going on (level from 1 to 10)", + sub { $run{verbose} = $_[0]; 1 }, "Setting verbose level" ], + [ "w", "warn", 0, "", + "Warn maintainer of the packages about problem in the rebuild", + sub { $run{warn} = 1; 1 }, "Setting warn flag to warn maintainers" ], + [ "", "shell", 0, "", + "Dump to a shell into the newly created chroot with sudo on rpm, urpmi, urpme and urpmi.addmedia", + sub { + ($run{shell}) = 1; + 1 }, "Setting option to dump to a shell" ], + [ "", "stop", 1, "<rpm step>", + "Perform rpm -b<rpm step> (p c i l b a s) instead of rpm -ba and then open a shell in the chroot", + sub { + ($run{stop}) = @_; + 1; + }, "Setting rpm build option" ], + [ "", "repository", 1, "<distribution root path>", + "Set a repository path if one is not created in the configuration file", + sub { + ($run{repository}) = @_; + 1; + } , "Setting the repository" ], + [ "", "status", 1, "<mail>", + "Send a status mail to the provided mail address", + sub { + ($run{status_mail}) = @_; + 1; + }, "Setting status mail option" ], + [ "", "with", 1, "<flag>", + "Use specified --with flag with rpm (can be used multiple times)", + sub { + ($run{with_flags}) = $run{with_flags} . " --with " . @_[0]; + 1; + }, "Adding specified extra --with parameter to rpm" ], + [ "", "without", 1, "<flag>", + "Use specified --without flag with rpm (can be used multiple times)", + sub { + ($run{with_flags}) = $run{with_flags} . " --without " . @_[0]; + 1; + }, "Adding specified extra --without parameter to rpm" ], +); + +open(my $LOG, ">&STDERR"); +$run{LOG} = sub { print $LOG @_ }; + +plog_init($program_name, $LOG, $run{verbose}, 1); +#plog_init($program_name, $LOG, 7, 1); # CM: hardcoded for now, will fix ASAP + + +# Display version information +# +(my $iurt_rev = '$Rev: 145712 $') =~ s/.*: (\d+).*/$1/; +(my $iurt_aut = '$Author: blino $') =~ s/.*: (..).*/$1/; +(my $iurt_dat = '$Date: 2007-03-18 09:25:20 -0300 (Sun, 18 Mar 2007) $') + =~ s/.*: ([\d-]* [\d:]*) .*/$1/; +plog("MSG", "This is iurt2 revision $iurt_rev-$iurt_aut ($iurt_dat)"); + + +my $todo = parseCommandLine($program_name, \@ARGV, \@params); +@ARGV and usage($program_name, \@params, "@ARGV, too many arguments"); +foreach my $t (@$todo) { + plog('DEBUG', $t->[2]); + &{$t->[0]}(@{$t->[1]}) or plog('ERR', $t->[2]); +} + +$run{distro_tag} = $run{distro}; +$run{distro_tag} =~ s,/,-,g; + +my $real_arch = `uname -m`; +chomp $real_arch; +my $HOME = $ENV{HOME}; +my $configfile = "$HOME/.iurt.$run{distro_tag}.conf"; + +plog('DEBUG', "load config: $configfile"); +my $config; +if (-f $configfile) { + $config = eval(cat_($configfile)) + or die "FATAL $program_name: syntax error in $configfile"; +} else { + $config = {}; +} + +if ($run{repository}) { + plog('DEBUG', "overriding configuration repository by the one given in the command line"); + $config->{repository} = $run{repository} +} + +if (!$config->{repository}) { + die "FATAL $program_name: no repository have been defined (use --repository to specify one on the command line" +} + +my $urpmi = Iurt::Urpmi->new(run => \%run, config => $config, urpmi_options => "-v --no-verify-rpm --nolock --auto --ignoresize"); +$run{urpmi} = $urpmi; + +if (!$run{chrooted_urpmi} && $run{group}) { + die "FATAL $program_name: option --chrooted-urpmi is mandatory if --group is selected"; +} + +my %config_usage = ( + admin => { + desc => 'Mail of the administrator of packages builds', + default => '' + }, + all_media => { + desc => 'List of known media', + default => { + 'main' => [ '' ], + 'contrib' => [ '' ] + } + }, + basesystem_media_root => { + desc => 'Name of the media holding basesystem packages', + default => sub { + my ($config, $run) = @_; + "$config->{repository}/$run->{distro}/$run->{my_arch}/"; + } + }, + basesystem_media => { + desc => 'Where to find basesystem packages', + default => 'main/release' + }, + basesystem_packages => { + desc => 'List of packages needed for the chroot creation', + default => [ + 'basesystem', + 'rpm-build', + 'rpm-mandriva-setup-build', + 'sudo', + 'urpmi', + 'curl', + ] + }, + build_timeout => { + desc => 'Maximum build time after which the build process is terminated', + default => { + default => 18000, + }, + }, + cache_home => { + desc => 'Where to store the cache files', + default => "$HOME/.bugs" + }, + cache_min_size => { + desc => 'Minimal size to consider a cache file valid', + default => 1000000 + }, + check_binary_file => { + desc => 'Packages rebuild should be checked, however sometime rpm is segfaulting and the test is not correct', + default => 0 + }, + iurt_root_command => { + desc => 'Program to run sudo command', + default => '/usr/sbin/iurt_root_command' + }, + distribution => { + desc => 'Name of the packages distribution', + default => 'Mandriva Linux' + }, + home => { + desc => 'Home dir', + default => $HOME + }, + install_chroot_binary => { + desc => 'Tool used to create initial chroot', + default => 'install-chroot-tar.sh' + }, + local_home => { + desc => 'Where to build packages', + default => $HOME + }, + local_upload => { + desc => 'Where to store build packages and log', + default => '' + }, + local_spool => { + desc => 'To override the directory where all the results are stored', + default => '' + }, + log_size_limit => { + desc => 'Maximum authorized size for a log file', + default => '100M' + }, + log_size_date => { + desc => 'Number of days log should be kept', + default => '30' + }, + log_url => { + desc => 'Where the log can be seen', + default => '' + }, + minimum_package_number => { + "Minimum number of packages in a synthesis file to consider it valid", + default => 1000 + }, + max_command_retry => { + "Maximum number of retry Iurt will perform for a given command", + default => 20 + }, + no_mail => { + desc => 'Hash table with people mail address where we should not send any mails', + default => {} + }, + packager => { + desc => 'Name of the build bot', + default => 'Iurt' + }, + prompt => { + desc => 'Default prompt in the chroot', + default => qq{PS1='[\\033[00;33m\\]iurt $run{distro}\\[\\033[00m\\]] \\[\\033[00;31m\\]\\u\\[\\033[00;32m\\]\\h\\[\\033[00m\\]\\w\$ '}, + }, + repository => { + desc => 'Prefix of the repositories', + default => '' + }, + rsync_to => { + desc => 'Server where the result of the builds should be rsynced (name@server:path format)', + default => '' + }, + sendmail => { + desc => 'If the bot will send mail reports regarding build', + default => 0 + }, + supported_arch => { + desc => 'Table of supported architecture', + default => ['i586', 'x86_64'] + }, + upload => { + desc => 'Where to copy build packages', + default => "$HOME/uploads/" + }, + vendor => { + desc => 'Name of the packages vendor', + default => 'Mandriva' + }, +); + +config_usage() if $run{config_usage}; +$run{my_arch} or usage($program_name, \@params, "no architecture given (media $run{media}, run{my_arch} $run{my_arch}, todo", join(', ', @{$run{todo}})); +if (!$arch_comp{$real_arch}{$run{my_arch}}) { + die "FATAL $program_name: could not compile $run{my_arch} binaries on a $real_arch"; +} +config_init(\%config_usage, $config, \%run); + +$config->{upload} .= $run{distro}; +$config->{upload} =~ s/community//g; +if ($run{distro} ne 'cooker') { + if ($run{media} ne 'main') { + $config->{upload} .= "/$run{media}"; + } +} elsif ($run{media} eq 'contrib') { + $config->{upload} =~ s/cooker/contrib/g; +} + +my $lock = $run{media}; +my $local; # FIXME: (tv) variable $local assigned, but not read +if (!$lock && $run{chroot}) { + $lock = 'chroot'; + $local = 1; +} +if (!$lock && $run{dkms}) { + $lock = 'dkms'; + $local = 0; +} +$run{lock} = $lock; + +# cache file name is needed early to remove the manual lock file if the +# lock mechanism does not work + +mkpath $config->{cache_home}; +my $cachefile = "$config->{cache_home}/iurt.$run{distro_tag}.$run{my_arch}.$lock.cache"; +$run{cachefile} = $cachefile; +if (!$run{debug} && $run{media} || $run{chroot}) { + $run{pidfile_home} = "$config->{cache_home}/"; + $run{pidfile} = "iurt.$run{distro_tag}.$run{my_arch}.$lock"; + check_pid(\%run); +} + +$config->{local_upload} ||= $config->{local_home}; +my $local_spool; +if ($config->{local_spool}) { + $local_spool = $config->{local_spool}; +} else { + $local_spool = "$config->{local_upload}/iurt/$run{distro_tag}/$run{my_arch}/$run{media}/"; +} + +# Squash double slashes +$local_spool =~ y!/!!s; + +plog('INFO', "local spool: $local_spool"); +if (!-d "$local_spool/log") { + plog('DEBUG', "creating local spool $local_spool"); + mkpath("$local_spool/log") + or die "FATAL: could not create local spool dir $local_spool ($!)"; +} +$run{local_spool} = $local_spool; + +my $cache; +my $clear_cache = 1; +if (-f $cachefile && $run{use_cache}) { + plog('INFO', "loading cache file $cachefile"); + + $cache = eval(cat_($cachefile)) + or plog('ERR', "FATAL: could not load cache $cachefile ($!)"); + + if (!$cache) { + opendir my $cache_dir, $config->{cache_home}; + my $to_load; + + foreach my $file (readdir $cache_dir) { + (my $date) = $file =~ /iurt\.$run{distro_tag}\.$run{my_arch}\.$run{media}\.cache\.tmp\.(\d{8})/ or next; + if ($date > $to_load && -s "$config->{cache_home}/$file" > $config->{cache_min_size}) { + $to_load = $date; + $cachefile = "$config->{cache_home}/$file"; + } + } + + plog('NOTIFY', "loading alternate cache file $cachefile"); + $cache = eval(cat_($cachefile)) + or plog('ERR', "FATAL: could not load cache $cachefile ($!)"); + } + $clear_cache = 0 if $cache; +} + +if ($clear_cache) { + $cache = { + rpm_srpm => {}, + failure => {}, + queue => {}, + warning => {}, + run => 1, + needed => {}, + no_unionfs => {} + }; +} +$run{cache} = $cache; + +my (%srpm_version, @wrong_rpm, %provides, %pack_provide, $to_compile, %maint); +$to_compile = @{$run{todo}}; +$to_compile += check_media(\%run, $cache, $config, \%srpm_version, + \@wrong_rpm, \%provides, \%pack_provide, \%maint) if $run{media}; +$to_compile += search_packages(1, $cache, \%provides, \%run, \%maint, + \%srpm_version, @{$run{extra_dir}}) if $run{extra}; + +my $dkms; +if ($run{dkms}) { + $dkms = Iurt::DKMS->new(run => \%run, config => $config); + $to_compile += $dkms->search_dkms; +} +$run{to_compile} = $to_compile; + +dump_cache_par(\%run); + +plog("Packages to build: $to_compile"); + +my ($fulldate, $daydate) = get_date(); +if ($run{use_cache}) { + $run{run} = $cache->{run}; + $run{run} ||= 1; + $cache->{run} = $run{run} + 1; +} else { + $run{run} = "0.$fulldate"; +} +$run{daydate} = $daydate; +plog('DEBUG', "using $run{run} as chroot extension"); +$run{user} = $ENV{SUDO_USER} || $ENV{USER}; +$run{uid} = getpwnam $run{user}; + +plog('DEBUG', "using local user $run{user}, id $run{uid}"); +my $luser = $run{user} || 'builder'; + +check_sudo_access() + or die "FATAL: you need to have sudo access to run $program_name"; + +my $debug_tag = $run{debug} && '_debug'; +$run{debug_tag} = $debug_tag; +if ($run{unionfs} && !$run{use_old_chroot}) { + plog(1, "adding unionfs module"); + sudo(\%run, $config, "--modprobe", "unionfs") or $run{unionfs} = 0; + if ($run{unionfs}) { + $run{unionfs_dir} = "$config->{local_home}/iurt_unionfs$debug_tag/"; + remove_chroot(\%run, $run{unionfs_dir}, \&clean_all_unionfs); + $run{unionfs_dir} = "$run{unionfs_dir}/$run{user}/"; + -d $run{unionfs_dir} or mkdir $run{unionfs_dir}; + } +} + +my (%done, $done); +$run{done} = \%done; +my $home = $config->{local_home}; +my $union_id = 1; +$run{unionfs_tmp} = $run{unionfs}; + +my ($chroot_name, $chroot_tmp, $chroot, $chroot_tar); +$chroot_name = "chroot_$run{distro_tag}$debug_tag"; +if (!$run{use_old_chroot}) { + $chroot_tmp = "$config->{local_home}/chroot_tmp"; + + if (!-d $chroot_tmp) { + mkdir $chroot_tmp; + } else { + remove_chroot(\%run, $chroot_tmp, \&clean_all_chroot_tmp, $chroot_name); + } + + $chroot_tmp = "$config->{local_home}/chroot_tmp/$run{user}"; + if (!-d $chroot_tmp) { + mkdir $chroot_tmp; + } + $chroot_tmp = "$config->{local_home}/chroot_tmp/$run{user}/$chroot_name.$run{run}"; + $run{chroot_tmp} = $chroot_tmp; + + $chroot = "$config->{local_home}/$chroot_name"; +} else { + plog(1, "using given chroot $run{use_old_chroot}"); + $chroot_tmp = $run{use_old_chroot}; + $chroot = $run{use_old_chroot}; +} +$run{chroot_path} = $chroot; +$chroot_tar = "$config->{local_home}/$chroot_name.$run{my_arch}.tar.gz"; +$run{chroot_tar} = $chroot_tar; +# 20061222 warly +# even in use_old_chroot mode we create the chroot if it does not exist (useful +# if the option is used for the first time +if ($run{chroot} || !-d "$chroot/dev") { + check_build_chroot($chroot, $chroot_tar, \%run, $config) or die "FATAL $program_name: could not prepare initial chroot"; +} + +# now exit if there is nothing to do and it was just a cleaning pass +if ($run{no_compile} || !@{$run{todo}} && !$run{debug} && !$run{shell} && !$run{rebuild}) { + send_status_mail(\%run, $config, $cache) if $run{status_mail}; + plog("no package to compile :("); + unlink "$run{pidfile_home}/$run{pidfile}" if $run{pidfile}; + exit(); +} + +plog('DEBUG', "running with pid $$"); +$run{prefix} = get_prefix($luser); + +my $df = df $home; +if ($df->{per} >= 99) { + die "FATAL: not enough space on the filesystem, only $df->{bavail} KB on $home, full at $df->{per}%"; +} + +if ($run{shell}) { + if (!$run{use_old_chroot}) { + ($union_id, my $chroot_tmp) = create_temp_chroot(\%run, $config, + $cache, $union_id, $chroot_tmp, $chroot_tar) + or die "FATAL $program_name: could not create temporary chroot"; + } + add_local_user($chroot_tmp, \%run, $config, $luser, $run{uid}) or die "FATAL $program_name: could not add local user"; + + #$urpmi->set_command($chroot_tmp); + $urpmi->urpmi_command($chroot_tmp, $luser); + + $urpmi->install_packages('chroot', $chroot_tmp, $local_spool, \%pack_provide, 'configure', "[ADMIN] installation of urpmi and sudo failed in the chroot $run{my_arch}", { check => 1, maintainer => $config->{admin} }, 'urpmi', 'sudo') or die "FATAL $program_name: could not add urpmi and sudo in the chroot"; + add_sudoers(\%run, $chroot_tmp, $luser); + if ($run{shell}) { + plog('NOTIFY', "dumping to a chrooted shell into $chroot_tmp"); + exec $sudo, 'chroot', $chroot_tmp, '/bin/su', '-', $luser, '-c', "$config->{prompt} bash"; + die "FATAL $program_name: could not exec chroot to $chroot_tmp ($!)"; + } +} + +# perform some cleaning before running to have some more space, rsync to +# the server too in case previous iurt crashed + +if ($config->{rsync_to} && !$run{no_rsync}) { + # remove some old and very big log files not to saturate the server + system(qq(find $local_spool/log/ -name "*.log" \\( -size +$config->{log_size_limit} -or -mtime +$config->{log_size_date} \\) -exec rm -f {} \\;)); + system('rsync', '--delete', '-alHPe', 'ssh -xc arcfour', "$local_spool/log/", "$config->{rsync_to}/$run{distro_tag}/$run{my_arch}/$run{media}/log/"); +} + +if ($run{dkms} && $run{dkms_todo}) { + $done += $dkms->dkms_compile($local_spool, $done); +} + +# The next loop should be moved in a module someday + +# FIXME: (tv) kill this dead code or use it!! +my $_s = sub { + if ($run{main}) { + plog("dumping cache..."); + dump_cache_par(\%run); + $Data::Dumper::Indent = 0; + $Data::Dumper::Terse = 1; + plog("Running environment:\n", Data::Dumper->Dump([\%run]), "\n"); + plog("Configuration:\n", Data::Dumper->Dump([$config]), "\n"); + } + exit(); +}; +#$SIG{TERM} = $s; +#$SIG{INT} = $s; +$run{main} = 1; + +my $rebuild; +$run{group} = 0 if @{$run{todo}} == 1; +if ($run{group}) { + $rebuild = 1; + $urpmi->set_local_media($local_spool); + $urpmi->order_packages($union_id, \%provides, $luser) + or die "FATAL $program_name: could not order packages"; +} +# +# The build loop +# +my $prev_done = $done; +do { + $rebuild = 0; + $done = $prev_done; + for (my $i; $i < @{$run{todo}}; $i++) { + my ($dir, $srpm, $status) = @{$run{todo}[$i]}; + + # CM: Set argv[0] (in the C sense) to something we can easily spot and + # understand in process list + $0 = "Iurt: $run{distro_tag} $run{my_arch} $run{media} $srpm"; + + $status or next; + $done{$srpm} and next; + $done{$srpm} = 1; + check_version($srpm, \%srpm_version) or next; + if ($run{debug}) { $run{debug}++ == 2 and exit() } + $done++; + plog('NOTIFY', "Build package $srpm [$done/$to_compile]"); + # FIXME unfortunately urpmi stalls quite often + my $retry = 0; + + # current rpm is sometime segfaulting, and iurt is them blocked + # and cannot + # + # $cache->{failure}{$srpm} = 1; + # dump_cache(\%run); +retry: + $urpmi->clean_urpmi_process; + + if (!$run{use_old_chroot}) { + (my $u_id, $chroot_tmp) = create_temp_chroot(\%run, $config, + $cache, $union_id, $chroot_tmp, $chroot_tar, $srpm) or next; + $union_id = $u_id; + } + + $urpmi->urpmi_command($chroot_tmp, $luser); + $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm$/ or next; + my ($maintainer, $cc); + if (!$run{warn}) { + ($maintainer) = get_maint(\%run, $srpm); + $cc = $maint{$srpm};#, maintainers\@mandriva.com"; + chomp $maintainer; + if (!$maintainer || $maintainer eq 'NOT_FOUND') { + $maintainer = $cc; + #$cc = 'maintainers@mandriva.com' + } + } + #($maintainer, $cc) = ($config->{admin},''); + + plog('DEBUG', "creating user $luser in chroot"); + add_local_user($chroot_tmp, \%run, $config, $luser, $run{uid}) or next; + + my $old_srpm = $srpm; + my ($ret, $srpm, $spec) = $urpmi->recreate_srpm(\%run, $config, + $chroot_tmp, $dir, $srpm, $luser, $retry); + if ($ret == -1) { + $retry = 1; + goto retry; + } elsif (!$ret) { + # CM: experimental: fail if we can't regenerate the srpm + # This should eliminate bouncers that block the input queue + # + $srpm = $old_srpm; + $cache->{failure}{$srpm} = 1; + $run{status}{$srpm} = 'recreate_srpm_failure'; + dump_cache_par(\%run); + dump_status($local_spool, \%run); + next; + } + + (my $log_dirname = $srpm) =~ s/.*:(.*)\.src.rpm/$1/; + my $log_dir = "$local_spool/log/$log_dirname/"; + + # only create the log dir for the new srpm + mkdir $log_dir; + -d $log_dir or die "FATAL: could not create $log_dir (check permissions and group ownerships)"; + + plog('INFO', "Install build dependencies"); + my $path_srpm = "$chroot_tmp/home/$luser/rpm/SRPMS/"; + + # on x86_64 the rpm database is getting corrupted and sometimes + # rpm do not found anymore installed packages, retrying several + # time to be sure something is really broken + + my $ok = $urpmi->install_packages($srpm, $chroot_tmp, $local_spool, \%pack_provide, 'install_deps', "[REBUILD] install of build dependencies of $srpm failed on $run{my_arch}", { maintainer => $maintainer }, "$path_srpm/$srpm"); + if (!$ok) { + $run{status}{$srpm} ||= 'install_deps_failure'; + next; + } + + # try to workarround the rpm -qa db4 error(2) from dbcursor->c_get: + # No such file or directory + # system("sudo chroot $chroot_tmp rm -rf /var/lib/rpm/__db* &> /dev/null"); + system("$sudo chroot $chroot_tmp rpm --rebuilddb &> /dev/null"); + + perform_command("$sudo chroot $chroot_tmp rpm -qa", + \%run, $config, $cache, + logname => "rpm_qa", + hash => "rpm_qa_$srpm", + timeout => 60, + debug_mail => $run{debug}, + log => $log_dir); # or next; As this failed quite often, do not stop + plog('NOTIFY', "Building $srpm"); + my $command = "rpm --rebuild $run{with_flags} /home/$luser/rpm/SRPMS/$srpm"; + if ($run{stop}) { + $urpmi->install_packages('chroot', $chroot_tmp, $local_spool, \%pack_provide, 'configure', "[ADMIN] installation of urpmi and sudo failed in the chroot $run{my_arch}", { check => 1, maintainer => $config->{admin} }, 'urpmi', 'sudo'); + add_sudoers(\%run, $chroot_tmp, $luser); + $command = "rpm -b$run{stop} /home/$luser/rpm/SPECS/$spec"; + } + + my ($srpm_name) = $srpm =~ /(?:.*:)?(.*)-[^-]+-[^-]+\.src\.rpm$/; + + if (!perform_command(qq(TMP=/home/$luser/tmp/ $sudo chroot $chroot_tmp /bin/su - $luser -c "$command"), + \%run, $config, $cache, + mail => $maintainer, + error => "[REBUILD] $srpm from $run{distro_tag} does not build correctly on $run{my_arch}", + logname => "build", + hash => "build_$srpm", + timeout => $config->{build_timeout}{$srpm_name} ? $config->{build_timeout}{$srpm_name} : $config->{build_timeout}{default}, + srpm => $srpm, + debug_mail => $run{debug}, + cc => $cc, + log => $log_dir, + error_regexp => 'rror.*ailed|Bad exit status|RPM build error', + callback => sub { + my ($opt, $output) = @_; + if ($run{stop}) { + plog("dumping to a chrooted shell into $chroot_tmp (pid $$)"); + # exec does not work because it seems stdin and out are shared between children + system($sudo, 'chroot', $chroot_tmp, '/bin/su', '-', $luser, '-c', "$config->{prompt} bash"); + exit(); + } + plog('DEBUG', "calling callback for $opt->{hash}"); + if ($run{unionfs_tmp} && $output =~ /no space left on device/i) { + plog('ERROR', "ERROR: running out of space to compile $srpm in unionfs mode, will recompile it in normal mode"); + $cache->{no_unionfs}{$srpm} = 1; + return 1; + } elsif ($run{unionfs_tmp} && $output =~ m,$home,) { + plog('ERROR', "ERROR: seems like building $srpm needs to access /proc/self/exe, which is broken with unionfs, will try to recompile it in non unionfs mode"); + $cache->{no_unionfs}{$srpm} = 1; + return 1; + } elsif ($output =~ /bin\/ld: cannot find -l(\S*)|configure.*error.* (?:-l([^\s]+)|([^\s]+) includes)/) { + my $missing = $1; + my @rpm = find_provides(\%run, \%pack_provide, $missing); + plog(5, "likely @rpm ($missing-devel) needed to rebuilt $srpm is not in build_requires"); + if ($maintainer ne 'NOT_FOUND') { + $opt->{mail} = $maintainer; + #$opt->{mail} .= ", other_maint"; + } + if (!$opt->{mail}) { + $opt->{mail} = $config->{admin}; + } + if (@rpm > 1) { + $opt->{error} = "[MISSING_BUILD_REQUIRES_TAG] one of @rpm ($missing-devel), needed to build $srpm, is not in buildrequires"; + } elsif (@rpm == 1) { + $opt->{error} = "[MISSING_BUILD_REQUIRES_TAG] @rpm ($missing-devel), needed to build $srpm, is not in buildrequires"; + } else { + $opt->{error} = "[MISSING_BUILD_REQUIRES_TAG] $missing-devel, needed to build $srpm, is not in buildrequires"; + } + $cache->{buildrequires}{$srpm}{$missing} = \@rpm; + return; + } + 1; + }, + freq => 1)) { + + # FIXME + # The simple algo used here is : + # try to compile it with unionfs, if it runs out of space, + # compile it without the next time + # + # This could be improved in keeping this srpm name for future + # version, but if we compile it on a new machine with more ram, + # or if next version compiles just fine with unionfs, we will + # loose the unionfs advantage. + # + # Maybe the right thing to do would be to first try to increase + # the tmpfs size (more than 50 % of the physical RAM), but this + # will lead to more swap usage, and slower compilation (and lost + # of the unionfs plus). Or to keep the faulty package a unionfs + # exception for some time, to save some more extra builds. + + if (!glob "$chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm") { + if ($run{unionfs_tmp} && $cache->{no_unionfs}{$srpm}) { + goto retry; + } + $cache->{failure}{$srpm} = 1; + $run{status}{$srpm} = 'build_failure'; + # 20060615 + dump_cache_par(\%run); + dump_status($local_spool, \%run); + next; + } + } + + # do some cleaning if the compilation is successful + delete $cache->{needed}{$srpm} if defined $cache->{needed}{$srpm}; + delete $cache->{buildrequires}{$srpm} if defined $cache->{buildrequires}{$srpm}; + # FIXME It seems the glob is not correctly expanded any more, so listing the directory content to do so + opendir my $binfh, "$chroot_tmp/home/$luser/rpm/RPMS/"; + my @packages; + foreach my $bindir (readdir $binfh) { + -d "$chroot_tmp/home/$luser/rpm/RPMS/$bindir" or next; + opendir my $rpmfh, "$chroot_tmp/home/$luser/rpm/RPMS/$bindir"; + push @packages, map { "$chroot_tmp/home/$luser/rpm/RPMS/$bindir/$_" } grep { !/src\.rpm$/ && /\.rpm$/ } readdir $rpmfh; + } + + # 20060810 warly We should fail here, but rpm is currently + # segfaulting when trying to install packages + + if ($config->{check_binary_file}) { + $urpmi->install_packages($srpm, $chroot_tmp, $local_spool, \%pack_provide, 'binary_test', "[REBUILD] binaries packages generated from $srpm do not install correctly", { maintainer => $maintainer } ,@packages) or next; + } else { + my $successfile = "$local_spool/log/$srpm/binary_test_$srpm-1.log"; + open my $f, ">$successfile"; + print $f "$srpm build ok"; + } + + $run{status}{$srpm} = 'ok'; + delete $cache->{failure}{$srpm} if defined $cache->{failure}{$srpm}; + if ($run{debug}) { + plog("debug mode, skip other packages"); + exit(); + } elsif ($run{group}) { + # we should not move the package until they are all compiled + plog("group mode, keep packages for local media ($srpm is done $done)"); + $run{done}{$srpm} = $done; + $urpmi->add_to_local_media($chroot_tmp, $srpm, $luser); + } else { + plog('OK', "build successful, copying packages to $local_spool."); + + system("cp $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm $local_spool &>/dev/null") and plog('ERR', "ERROR: could not copy rpm files from $chroot_tmp/home/$luser/rpm/RPMS/ to $local_spool ($!)"); + + if ($run{copy_srpm}) { + # replace the old srpm + unlink "$local_spool/$old_srpm"; + + system("cp $chroot_tmp/home/$luser/rpm/SRPMS/$srpm $local_spool &>/dev/null") and plog('ERR', "ERROR: could not copy $srpm from $chroot_tmp/home/$luser/rpm/SRPMS/ to $local_spool ($!)"); + } + process_queue($config, \%run, \@wrong_rpm, 1); + } + # dymp_cache each time so that concurrent process can get updated + dump_cache_par(\%run) if $run{concurrent_run}; + } + if ($run{group}) { + for (my $i; $i < @{$run{todo}}; $i++) { + my (undef, $srpm) = @{$run{todo}[$i]}; + if (!$run{done}{$srpm}) { + $rebuild = $urpmi->order_packages($union_id, \%provides, $luser); + last + } + } + if ($prev_done == $done) { + $rebuild = 0; + if ($done == @{$run{todo}}) { + plog('OK', "all packages succesfully compiled, copying packages to $local_spool."); + system("cp $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm $local_spool &>/dev/null") and plog('ERR', "ERROR: could not copy rpm files from $chroot_tmp/home/$luser/rpm/RPMS/ to $local_spool ($!)"); + if ($run{copy_srpm}) { + system("cp $chroot_tmp/home/$luser/rpm/SRPMS/*.src.rpm $local_spool &>/dev/null") and plog('ERR', "ERROR: could not copy SRPMS from $chroot_tmp/home/$luser/rpm/SRPMS/ to $local_spool ($!)"); + } + } else { + plog('FAIL', "some packages could not be compiled."); + } + } + } +} while $rebuild; + +my ($unionfs_dir) = $run{unionfs_dir} =~ m!(.*)/[^/]+/?!; +if (!$run{debug} && !$run{use_old_chroot}) { + if ($run{unionfs}) { + clean_unionfs("$unionfs_dir/$run{user}", \%run, $run{run}, $union_id); + } else { + clean_chroot($chroot_tmp, $chroot_tar, \%run, $config, 1); + } +} +plog("reprocess generated packages queue"); +process_queue($config, \%run, \@wrong_rpm); + +dump_cache_par(\%run); + +plog('FAIL', "ERROR: RPM with a wrong SRPM name") if @wrong_rpm; +if (@wrong_rpm && open my $file, ">$local_spool/log/wrong_srpm_names.log") { + foreach (@wrong_rpm) { + print $file "$_->[1] -> $_->[0] (", $cache->{rpm_srpm}{$_->[1]}, ")\n"; + } +} + +dump_status($local_spool, \%run); + +send_status_mail(\%run, $config, $cache) if $run{status_mail}; + +if ($config->{rsync_to} && !$run{no_rsync}) { + # remove some old and very big log files not to saturate the server + system(qq(find $local_spool/log/ -name "*.log" \\( -size +$config->{log_size_limit} -or -mtime +$config->{log_size_date} \\) -exec rm -f {} \\;)); + system('rsync', '--delete', '-alHPe', 'ssh -xc arcfour', "$local_spool/log/", "$config->{rsync_to}/$run{distro_tag}/$run{my_arch}/$run{media}/log/"); +} + +# one last try to clean +plog('DEBUG', "clean remaining unionfs"); +if ($run{unionfs} && !$run{use_old_chroot}) { + remove_chroot(\%run, $unionfs_dir, \&clean_all_unionfs); +} +unlink "$run{pidfile_home}/$run{pidfile}" if $run{pidfile}; + +exit; + + +# +# +# + +sub check_needed { + my ($srpm, $cache, $provides) = @_; + if (!defined $cache->{needed}{$srpm} && !ref $cache->{needed}{$srpm}) { return 1 } + my $ok = 1; + # migrate old cache format + my $ent = $cache->{needed}{$srpm}; + if (ref $ent eq 'ARRAY') { + my $table = $ent; + $cache->{needed}{$srpm} = {}; + foreach my $t (@$table) { + my ($missing, $version, $maint) = @$t; + $cache->{needed}{$srpm}{$missing} = { + version => $version, + maint => $maint + }; + } + $ent = $cache->{needed}{$srpm}; + } + foreach my $name (keys %$ent) { + my ($package, $version, $maint) = @{$ent->{$name}}{'package', 'version', 'maint'}; + # if packages does not exist anymore, it may have been rebuild, then try to recompute the build dependencies + last if $package && !$provides->{$package}; + my $p_version = $provides->{$name}; + if ($p_version) { + next if $version == $p_version; + next if URPM::ranges_overlap($version, $p_version); + } + $ok = 0; + if ($version) { + $ent->{$name}{version} = $version; + } + my $v ||= $version; + if ($package) { + plog("ERROR: $srpm needs package $package which requires missing $name $v to be compiled."); + } else { + plog("ERROR: $srpm needs $name $v to be compiled."); + } + # try to recompile it once in a while + last if $cache->{warning}{"install_deps_$srpm"}{$maint}++ % 72; + return 1; + } + delete $cache->{needed}{$srpm} if $ok; + $ok; +} + +sub process_queue { + my ($config, $run, $wrong_rpm, $quiet) = @_; + return if !$run->{upload} && $quiet; + my $dir = "$config->{local_upload}/iurt/$run->{distro_tag}/$run->{my_arch}/$run->{media}/"; + opendir my $rpmdir, $dir or return; + my $urpmi = $run->{urpmi}; + foreach my $rpm (readdir $rpmdir) { + my ($rarch, $srpm) = $urpmi->update_srpm($dir, $rpm, $wrong_rpm); + $rarch or next; + plog($rpm); + next if !$run->{upload}; + # recheck if the package has not been uploaded in the meantime + my $rpms_dir = "$config->{repository}/$run->{distro}/$run->{my_arch}/media/$run->{media}/"; + if (! -f "$rpms_dir/$rpm") { + my $err = system('/usr/bin/scp', "$dir/$rpm", $config->{upload} . "/$config->{extra_subdir}/RPMS/"); + # try to keep the opportunity to prevent disk full + if ($err) { + plog("ERROR: process_queue: cannot copy $dir/$rpm to ", $config->{upload}, "/$config->{extra_subdir}/RPMS/ ($!)"); + next; + } + } + if ($run->{upload_source}) { + + } + unlink "$dir/$rpm"; + $cache->{queue}{$srpm} = 1; + } + closedir $rpmdir; +} + +sub check_version { + my ($srpm, $srpm_version) = @_; + my ($srpm_name) = $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm/; + if (URPM::ranges_overlap("= $srpm", ">= $srpm_version->{$srpm_name}")) { + $srpm_version->{$srpm_name} = $srpm; + return 1; + } + 0; +} + +sub check_pid { + my ($run, $local) = @_; + my $hostname = `hostname`; + chomp $hostname; + my $pidfile = $run->{pidfile}; + my $lockfile = "$run->{pidfile_home}/$pidfile.$hostname.pid.lock"; + plog("trying to lock $lockfile"); + open my $lock, ">$lockfile"; + my $lock_ok; + # lockf seems not to work, try to workarround, but this start to create lock on the lock for the lock of the file. + my $status = 1; #File::lockf::lock($lock); + if (!$status) { + $lock_ok = 1; + } else { + plog("ERROR: could not lock pid file (status $status $!)"); + if (! -f "$lockfile.2") { + plog("using $lockfile.2 as lock file"); + open my $lock2, ">$lockfile.2" or die "FATAL $program_name: could not open lock file $lockfile.2"; + print $lock2 $$; + close $lock2; + } + } + if (!$run->{concurrent_run} && !$local) { + opendir my $dir, $run->{pidfile_home}; + foreach my $f (readdir $dir) { + my ($pid_host) = $f =~ /$pidfile\.pid\.(.*)\.pid$/ or next; + if ($pid_host ne $hostname) { + my $pf = "$run->{pidfile_home}/$f"; + open my $test_PID, $pf; + my $pid = <$test_PID>; + my (@stat) = stat $pf; + my $time = $stat[9]; + my $diff = time()-$time; + my $msg = "$program_name: an other iurt is running for $run->{my_arch} on $pid_host, pid $pid, since $diff seconds"; + if ($diff < 36000) { + plog("$msg\n"); + exit(); + } else { + plog("$msg, ignoring it"); + } + } + } + } + $run->{pidfile} .= ".$hostname.pid"; + $pidfile = "$run->{pidfile_home}/$run->{pidfile}"; + if (-f $pidfile) { + my (@stat) = stat $pidfile; + open my $test_PID, $pidfile; + my $pid = <$test_PID>; + close $test_PID; + if (!$pid) { + plog("ERROR: invalid pidfile ($pid), should be <pid>"); + unlink $pidfile; + } + if ($pid && getpgrp $pid != -1) { + my $time = $stat[9]; + my $state = `ps h -o state $pid`; + chomp $state; + if ($time < time()-36000 || $state eq 'Z') { + plog("an other iurt pid $pid is running for a very long time or is zombie, killing it"); + my $i; + while ($i < 5 && getpgrp $pid != -1) { + kill_for_good($pid); + $i++; + sleep 1; + } + } else { + plog("an other iurt is running for $run->{my_arch}, pid $pid, since ", time()-$time, " seconds"); + exit(); + } + } else { + plog("a previous iurt for $run->{my_arch} seems dead, cleaning."); + unlink $pidfile; + } + } + plog("setting $pidfile pid lock"); + open my $PID, ">$pidfile" or die "FATAL $program_name: could not open pidfile $pidfile for writing"; + print $PID $$; + close $PID; + if ($lock_ok) { + File::lockf::ulock($lock); + } else { + unlink "$lockfile.2"; + } + close $lock; + unlink $lockfile; +} + +sub check_media { + my ($run, $cache, $config, $srpm_version, $wrong_rpm, $provides, $pack_provide, $maint) = @_; +# We could rely on only parsing the synthesis, hoping that they are correct, however this scan is very fast, so... + foreach my $subdir (@{$config->{all_media}{$run->{media}}}) { + my $rpms_dir = "$config->{repository}/$run->{distro}/$run->{my_arch}/media/$run->{media}/$subdir/"; + plog("checking current packages in $rpms_dir"); + opendir my $rpmdir, $rpms_dir or die "Could not open $rpms_dir: $!"; + my $urpmi = $run->{urpmi}; + foreach my $rpm (readdir $rpmdir) { + my ($rarch, $srpm) = $urpmi->update_srpm($rpms_dir, $rpm, $wrong_rpm); + $rarch or next; + $cache->{queue}{$srpm} = 1; + $run{status}{$srpm} = 'ok'; + check_version($srpm, $srpm_version); + } + closedir $rpmdir; + } + + foreach my $m (keys %{$config->{all_media}}) { + foreach my $subdir (@{$config->{all_media}{$m}}) { + my $synthesis_file = "$config->{repository}/$run->{distro}/$run->{my_arch}/media/$m/$subdir/media_info/synthesis.hdlist.cz"; + if (-f $synthesis_file) { + plog("Parsing $synthesis_file"); + if (open my $syn, "zcat $synthesis_file |") { + my @prov; + my $nb; + while (<$syn>) { + if (/^\@provides@(.*)/) { + foreach my $p (split '@', $1) { + if ($p =~ /([^[]+)(?:\[(.*)\])?/g) { + push @prov, $1; + $provides->{$1} = $2 || 1; + } + } + } elsif (/\@info\@([^@]+)@/) { + $nb++; + my $p = $1; + my ($name) = $p =~ /(.*)-[^-]+-[^-]+\./; + $provides->{$p} = 1; + foreach (@prov) { + $pack_provide->{$_} = $name; + } + @prov = (); + } + } + $nb < $config->{minimum_package_number} and die "FATAL $program_name: synthesis files seems corrupted, only $nb packages found."; + } else { + die "FATAL $program_name: Could not open $synthesis_file\n"; + } + } + } + } + #" + my $nb; + foreach my $subdir (@{$config->{all_media}{$run->{media}}}) { + $nb += search_packages(0, $cache, $provides, $run, $maint, $srpm_version, "$config->{repository}/$run->{distro}/SRPMS/$run->{media}/$subdir/"); + } + $nb; +} + +sub search_packages { + my ($clean, $cache, $provides, $run, $_maint, $srpm_version, @dir) = @_; + my ($to_compile, %rep); + plog("iurt search_package: @dir"); + foreach my $dir (@dir) { + plog("checking SRPMS dir $dir"); + opendir my $rpmdir, $dir or next; + foreach my $srpm (readdir $rpmdir) { + # this is for the output of the new svn system + if ($srpm =~ /^\@\d+:(.*)/) { + link "$dir/$srpm", "$dir/$1"; + # unlink "$dir/$srpm"; + $srpm = $1; + } + $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm$/ or next; + $run->{status}{$srpm} ||= 0; + if ($config->{unwanted_packages} && $srpm =~ /$config->{unwanted_packages}/) { next } + my $ok = 1; + if (check_version($srpm, $srpm_version)) { + if (!$run->{ignore_failure} && defined $cache->{failure}{$srpm}) { + $run->{status}{$srpm} = 'build_failure'; + next; + } + my $check_needed = check_needed($srpm, $cache, $provides); + $run->{status}{$srpm} = 'missing_buildrequires' if !$check_needed; + -f "$dir/$srpm" or next; + if (!$cache->{queue}{$srpm} && $check_needed) { + if (!check_arch("$dir/$srpm", $run{my_arch})) { + $run->{status}{$srpm} = 'not_on_this_arch'; + next; + } + my $hdr = RPM4::Header->new("$dir/$srpm"); + my $changelog = $hdr->queryformat("%{CHANGELOGNAME}"); + my ($mail) = $changelog =~ /<(.*@.*)>/; + $maint{$srpm} = $mail; + print "$program_name: will try to compile $srpm\n"; + $to_compile++; + push @{$run->{todo}}, [ $dir , $srpm, 1 ]; + } + foreach my $arch (@{$config->{supported_arch}}) { #FIXME: (tv) this loop looks suspiciously broken + $ok &&= $cache->{queue}{$srpm}; + } + } + if ($clean && ($rep{$srpm} || $ok)) { + print "$program_name: cleaning $dir/$srpm\n"; + unlink "$dir/build/$srpm"; + unlink "$dir/$srpm"; + } + $rep{$srpm} = 1; + } + closedir $rpmdir; + } + $to_compile; +} + +sub add_sudoers { + my ($_run, $chroot, $user) = @_; + my $file = "$chroot/etc/sudoers"; + my $f; + if (!open $f, qq(| $sudo sh -c "cat > $file")) { + plog("ERROR: could not open $file ($!)"); + return 0; + } + print $f qq(Cmnd_Alias RPM=/bin/rpm,/usr/sbin/urpmi,/usr/sbin/urpme,/usr/sbin/urpmi.addmedia,/usr/sbin/urpmi.update,/usr/sbin/urpmi.removemedia +root ALL=(ALL) ALL +$user ALL=(ALL) NOPASSWD:RPM +); + close $f; + plog("adding sudo for /bin/rpm, /usr/sbin/urpmi and /usr/sbin/urpme"); + -f $file or return 0; + 1; +} + +sub dump_status { + my ($local_spool, $run) = @_; + my $media = $run->{media} ? "$run->{media}." : ""; + if (open my $file, ">$local_spool/log/status.${media}log") { + foreach my $srpm (sort keys %{$run->{status}}) { + print $file "$srpm: "; + if ($run{status}{$srpm}) { + print $file $run->{status}{$srpm}; + } else { + print $file "unknown"; + } + print $file "\n"; + } + } +} + +# +# CM: FIXME: should notify in case of recreate_srpm_failure +# + +sub send_status_mail { + my ($run, $config, $cache) = @_; + my %output; + + print "iurt compilation status\n"; + + foreach my $rpm (keys %{$run->{status}}) { + next if $run->{status}{$rpm} =~ /ok|not_on_this_arch/; + + if ($run->{status}{$rpm} eq 'missing_buildrequires') { + foreach my $missing (keys %{$cache->{needed}{$rpm}}) { + my $h = $cache->{needed}{$rpm}{$missing}; + my $maint = $h->{maint} || 'Other'; + my $package = $h->{package}; + if ($package) { + push @{$output{missing}{$maint}{$package}{$missing}{$h->{version}}}, $rpm; + } else { + $output{missing}{$maint}{$rpm}{$missing}{$h->{version}} = 1; + } + } + } elsif ($run->{status}{$rpm} eq 'build_failure') { + my ($maint) = get_maint($run, $rpm); + if ($cache->{buildrequires}{$rpm}) { + push @{$output{buildrequires}{$maint}}, $rpm; + } else { + push @{$output{build}{$maint}}, $rpm; + } + } elsif (!$run->{status}{$rpm}) { + # need to find something more usefull to do at that point + next; + } + } + + my $text = "*** Missing buildrequires tag in specfile ***\n"; + foreach my $maint (keys %{$output{buildrequires}}) { + $text .= "\n$maint\n"; + foreach my $pack (keys %{$output{missing}{$maint}}) { + foreach my $missing (keys %{$cache->{buildrequires}{$pack}}) { + my $rpms = $cache->{buildrequires}{$pack}{$missing}; + if (@$rpms) { + $text .= " $pack should have a buildrequires on @$rpms (for $missing-devel)\n"; + } else { + $text .= " $pack should have a buildrequires for $missing-devel\n"; + } + } + } + } + + $text = "*** Missing dependencies ***\n"; + foreach my $maint (keys %{$output{missing}}) { + $text .= "\n$maint\n"; + foreach my $pack (keys %{$output{missing}{$maint}}) { + foreach my $missing (%{$output{missing}{$maint}{$pack}}) { + my $h = $output{missing}{$maint}{$pack}{$missing}; + foreach my $version (keys %$h) { + if (ref $h->{$version}) { + $text .= " $pack should be recompile because\n $missing " . ($version ? "$version " : '') . "is not provided anymore\n"; + $text .= " to compile " . join("\n ", @{$h->{$version}}) . "\n"; + } else { + $text .= " $pack needs $missing " . ($version ? "$version " : '') . "\n"; + } + } + } + } + } + $text .= "\n*** Build failure ***\n"; + foreach my $maint (keys %{$output{build}}) { + $text .= "\n$maint\n"; + foreach my $rpm (@{$output{build}{$maint}}) { + $text .= " $rpm (see $config->{log_url}/$run{distro_tag}/$run{my_arch}/$run->{media}/log/$rpm/)\n"; + } + } + print "$text\n"; + sendmail($run->{status_mail}, '' , "Iurt report for $run->{my_arch}/$run->{media}", $text, "Iurt the rebuild bot <$config->{admin}>", 0); +} + +sub find_provides { + my ($_run, $pack_provide, $p) = @_; + my @rpm; + foreach my $provides (keys %{pack_provide}) { + if ($provides =~ /$p/ && $provides =~ /devel/) { + push @rpm, $pack_provide->{$provides}; + } + } + @rpm; +} + +sub check_sudo_access() { + open my $right, "$sudo -l |"; + return 1 if ! $<; + local $_; + while (<$right>) { + /\(ALL\)\s+NOPASSWD:\s+ALL/ and return 1; + } + 0; +} + +__END__ + +Discussion + +20061222 Warly + Group building + For the group building, we need to order the source packages, the problem is that we do not + really know what will be the provides of the resulting packages before building the. + We could guess them by looking to older version, but that means that we need to have an access to + the media deps files (synthesis should be enough). + We can also perform a first pass of build to check which package build and then what are their + provides. For the second pass, we will them be able to use the previously build packages to + solve buildrequires. @@ -0,0 +1,786 @@ +#!/usr/bin/perl +# +# Copyright (C) 2005,2006 Mandriva +# +# Author: Florent Villard <warly@mandriva.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# compare and rebuild packages on different architecture +# +# TODO +# +# - create a configuration file to handle the various iurt running +# - get the content of the rebuild dir +# - run as many iurt as machines are available and gather results +# - the scheduler just take the results, launch new rebuild, and quit +# - use perl ssh and try to workarround the non working timeout when the +# remote machine is stalled +# - use submitter as packager, not generic name +# + +use strict; +use MDK::Common; +use Iurt::Config qw(config_usage get_date config_init dump_cache init_cache get_repsys_conf check_arch check_noarch); +use Iurt::Process qw(check_pid); +use Iurt::File qw(check_upload_tree); +use Iurt::Mail qw(sendmail); +use Iurt::Util qw(plog_init plog ssh_setup ssh sout sget sput); +use File::Copy 'move'; +use File::Path 'mkpath'; +use File::Temp 'mktemp'; +use Filesys::Df qw(df); +use Data::Dumper; + +my %run; +my $program_name = 'ulri'; +$run{program_name} = $program_name; + +open(my $LOG, ">&STDERR"); +$run{LOG} = sub { print $LOG @_ }; + +plog_init($program_name, $LOG, 7, 1); + +my $HOME = $ENV{HOME}; +my $configfile = "$HOME/.upload.conf"; + +my $config; +if (-f $configfile) { + $config = eval(cat_($configfile)) or die "FATAL $program_name: syntax error in $configfile"; +} else { + $config = {}; +} + +my %config_usage = ( + admin => { + desc => 'mail address of the bot administrator', + default => 'warly@mandriva.com' + }, + 'arch_translation' => { + desc => "Renaming of arch", + default => { 'sparc64' => 'sparcv9' } + }, + bot => { + desc => "List of bot able to compile the packages", + default => { + i586 => { + n1 => { + iurt => { + user => 'mandrake' , + command => 'sudo -u mandrake -H /usr/local/bin/iurt2.sh --copy_srpm --group -v 1 --config local_spool /export/home/mandrake/iurt/__DIR__ --no_rsync --chrooted-urpmi http://kenobi/dis/ -r __TARGET__ __ARCH__', + packages => '/export/home/mandrake/iurt/', + log => '/export/home/mandrake/iurt/', + } , + }, + }, + }, + }, + media => { + desc => 'Corresponding media to add given the current media', + default => { + default => { + "main/release" => [ "main/release", "main/updates" ], + "main/updates" => [ "main/release", "main/updates" ], + "main/testing" => [ "main/release", "main/updates", + "main/testing" ], + "main/backports" => [ "main/release", "main/updates", + "main/testing", "main/backports" ], + "contrib/release" => [ "main/release", "main/updates", + "contrib/release", "contrib/updates" ], + "contrib/updates" => [ "main/release", "main/updates", + "contrib/release", "contrib/updates" ], + "contrib/testing" => [ "main/release", "main/updates", + "main/testing", "contrib/release", "contrib/updates", + "contrib/testing" ], + "contrib/backports" => [ "main/release", "main/updates", + "main/testing", "main/backports", "contrib/release", + "contrib/updates", "contrib/testing", + "contrib/backports" ], + "non-free/release" => [ "main/release", "main/updates", + "non-free/release", "non-free/updates" ], + "non-free/updates" => [ "main/release", "main/updates", + "non-free/release", "non-free/updates" ], + "non-free/testing" => [ "main/release", "main/updates", + "main/tessting", "non-free/release", + "non-free/updates", "non-free/testing" ], + "non-free/backports" => [ "main/release", "main/updates", + "main/testing", "main/backports", "non-free/release", + "non-free/updates", "non-free/testing", + "non-free/backports" ], + }, + }, + }, + default_mail_domain => { + desc => "Default mail domain to append", + default => 'mandriva.org' + }, + faildelay => { + desc => "Time after which the rebuild is considered as a failure", + default => 36000 + }, + http_queue => { + desc => 'Address where log can be consulted', + default => 'http://kenobi.mandriva.com/queue/' + }, + queue => { + desc => "Root of the tree where the packages to compile are located", + default => "/home/mandrake/uploads/" + }, + cache_home => { + desc => 'Where to store the cache files', + default => "$HOME/.bugs" + }, + repsys_conf => { + desc => 'Path of repsys.conf which includes login mail corresponding', + default => '/etc/repsys.conf' + }, + tmp => { + desc => "Temporary directory", + default => "$HOME/tmp/" + }, + ssh_options => { + desc => "SSH options", + default => "-o ConnectTimeout=20 -o BatchMode=yes" + }, + packager => { + desc => 'Default packager tag user by bot', + default => 'Mandriva Team <http://www.mandrivaexpert.com>' + }, +); +config_usage(\%config_usage, $config) if $run{config_usage}; +config_init(\%config_usage, $config, \%run); + +my %untranslated_arch; +foreach my $k (keys %{$config->{arch_translation}}) { + my $v = $config->{arch_translation}{$k}; + push @{$untranslated_arch{$v}}, $k; +} + +my $mail = get_repsys_conf($config->{repsys_conf}); + +$run{pidfile_home} = $config->{tmp}; +$run{pidfile} = "upload"; +my $pidfile = check_pid(\%run); + +#my $cache = init_cache(\%run, $config, { done => {} }); +my $cache = { done => {} }; + +my ($fulldate, $daydate) = get_date(); +$run{daydate} = $daydate; + +my $df = df $config->{queue}; +if ($df->{per} == 100) { + # FIXME should send a mail too + die "FATAL $program_name: not enough space on the filesystem, only $df->{bavail} KB on $config->{queue}, full at $df->{per}%"; +} + +($fulldate, $daydate) = get_date(); + +my %pkg_tree; +my $compildone = $cache->{done}; + +my $todo = "$config->{queue}/todo/"; +my $failure = "$config->{queue}/failure/"; +my $done = "$config->{queue}/done/"; + +# Raise this when the noarch package starts to build on any bot +my %noarch_build; + +# +# Part 0: gather data from upload tree +# + +plog('MSG', "check uploads tree"); + +# A list of what is currently building so we can report at the end +# +my %build_list; + +plog('DEBUG', "input queue is $todo"); + +sub todo_func { + my ($todo, $f, $m, $s, $r) = @_; + + if ($r =~ /(\d{14}\.(\w+)\.\w+\.\d+)_(.*\.src\.rpm)$/) { + my ($prefix, $user, $srpm) = ($1, $2, $3); + + plog('DEBUG', "found srpm $srpm ($prefix)"); + $pkg_tree{$prefix}{path} = "/$f/$m/$s"; + $pkg_tree{$prefix}{media} = "$m/$s"; + $pkg_tree{$prefix}{target} = $f; + $pkg_tree{$prefix}{user} = $user; + push @{$pkg_tree{$prefix}{srpms}} , $srpm; + my ($name) = $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm$/; + + return $pkg_tree{$prefix}{srpm_name}{$name} = $srpm; + } + + if ($r =~ /(\d{14}\.\w+\.\w+\.\d+)_([\w-]+)\.(\w+)\.(\w+)\.(\d{14})\.(\d+)\.lock$/) { + my ($prefix, $arch, $bot, $host, $date, $pid) = ($1, $2, $3, $4, $5, $6); + + $arch = $config->{arch_translation}{$arch} if $config->{arch_translation}{$arch}; + plog('DEBUG', "found lock on $host/$arch for $prefix"); + + # Only for build status reporting + # + push @{$build_list{"$host/$arch"}}, $prefix; + + if ($arch =~ /noarch/) { + plog('DEBUG', "... and $prefix is noarch"); + $noarch_build{$prefix} = 1; + $arch =~ s/-.*//; + } + + $run{bot}{$arch}{$host}{$bot} = $prefix; + + # this should be in the cache, but waiting for a cache-clean option + $compildone->{$prefix}{$arch} = 1; + + my $time = read_line("$todo/$f/$m/$s/$r"); + $time = (split ' ', $time)[2]; + push @{$pkg_tree{$prefix}{bot}}, { + bot => $bot, + host => $host, + date => $date, + pid => $pid, + 'arch' => $arch, + 'time' => $time + }; + } +} + +sub todo_post { + my ($todo, $f, $m, $s, $r) = @_; + + if ($r =~ /([^_]*)_(.*).lock$/) { + if (!$pkg_tree{$1}{srpms}) { + plog('INFO', "cleaning orphan $r"); + unlink "$todo/$f/$m/$s/$r"; + } + } +} + +sub done_func { + my ($_todo, $_f, $_m, $_s, $r) = @_; + + if ($r =~ /(\d{14}\.\w+\.\w+\.\d+)_(.*)\.(done|fail|excluded)$/) { + my ($prefix, $arch) = ($1, $2); + $arch = $config->{arch_translation}{$arch} if $config->{arch_translation}{$arch}; + $compildone->{$prefix}{$arch} = 1; + } elsif ($r =~ /(\d{14}\.\w+\.\w+\.\d+)_(.*\.([^.]+)\.rpm)$/) { + my ($prefix, $rpm) = ($1, $2); + plog('DEBUG', "found already built rpm $rpm ($prefix)"); + push @{$pkg_tree{$prefix}{rpms}} , $rpm; + } +} + + +check_upload_tree(\%run, $todo, \&todo_func, \&todo_post); + +# getting already compiled packages +# The cache should not be needed if the .done file are removed as the same +# time as the src.rpm in the todo tree +check_upload_tree(\%run, $done, \&done_func,); + + +# +# Part 1: get results from finished builds +# + +plog('MSG', "check build bot results"); + +my %later; +foreach my $prefix (keys %pkg_tree) { + my $ent = $pkg_tree{$prefix}; + my $path = $ent->{path}; + my $user = $ent->{user}; + + # Local pathnames + my $done_dir = "$done/$path"; + my $todo_dir = "$todo/$path"; + my $fail_dir = "$failure/$path"; + + bot: foreach my $bot_list (@{$ent->{bot}}) { + my ($bot, $host, $date, $pid, $arch, $time) = + @$bot_list{qw(bot host date pid arch time)}; + + my $bot_conf = $config->{bot}{$arch}{$host}{$bot}; + my $remote = ssh_setup($config->{ssh_options}, + $bot_conf->{user}, $host); + + # If our build is noarch, set arch appropriately. + # + my $lock_file = + "$todo_dir/${prefix}_$arch-noarch.$bot.$host.$date.$pid.lock"; + + if (-f $lock_file) { + plog('DEBUG', "$prefix is noarch"); + $arch = "noarch"; + } else { + $lock_file =~ s/-noarch//; + } + + my $prefix_dir = "$bot_conf->{packages}/$path/$prefix/"; + my $status_file = "$prefix_dir/log/status.log"; + + plog('INFO', "check status: $host/$arch ($bot [$pid])"); + my $status = sout($remote, "cat $status_file"); + my $success; + my $fail; + my $later; + + # Check if the build bot finished on the other side + # + if ($status) { + plog('INFO', "check result: $host/$arch ($bot [$pid])"); + foreach my $res (split "\n", $status) { + my ($p, $r) = $res =~ /(.*):\s+(.*)/; + plog('DEBUG', $res); + if ($r eq 'install_deps_failure') { + plog('FAIL', "install deps failure, rebuild later: $p"); + $later{$prefix} = 1; + $later = 1; + } + if ($r ne 'ok') { + plog('FAIL', "$r: $p"); + $fail = 1; + } + } + + if (!$fail) { + my @list = split "\n", sout($remote, "ls $prefix_dir"); + my $error; + + my $arch_check = join '|', $arch, if_($untranslated_arch{$arch}, @{$untranslated_arch{$arch}}); + plog('MSG', "checking for $arch_check arch"); + foreach my $result (@list) { + $result =~ /\.(src|$arch_check|noarch)\.rpm$/ or next; + + # do not copy the initial src package + $result =~ /^$prefix/ and next; + + my $result_file = "$done_dir/${prefix}_$result"; + my $done_file = "$done_dir/${prefix}_$arch.done"; + + plog('OK', "build ok: $result"); + $compildone->{$prefix}{$arch} = 1; + + plog('DEBUG', "copy files to done"); + mkpath($done_dir); + if (sget($remote, "$prefix_dir/$result", + "$result_file.new")) { + plog('ERR', "copying $result from $host failed ($!)"); + $error = 1; + last; + } elsif (move("$result_file.new", $result_file)) { + create_file($done_file, "$bot $host"); + $success = 1; + } + } + next if $error; + + # Do nothing if the scp failed, wait for the other run + # (it may be a disk full problem) + # next if $error; + #if ($success) { + # The SRPM should be recreated thus no need to copy it + # foreach my $srpm (@{$ent->{srpms}}) { + # plog("linking $todo_dir/${prefix}_$srpm to $done_dir/${prefix}_$srpm"); + # link "$todo_dir/${prefix}_$srpm", "$done_dir/${prefix}_$srpm" + # } + # FIXME Have to remove remote remaining packages and directory + #} + } + } # if ($status) + + # + # Handle build failure + # + + my $proc_state; + if (!$fail) { + chomp($proc_state = sout($remote, "ps h -o state $pid")); + } + + my $seconds = time()-$time; + + # Reasons for failure + my $timeout = $seconds > $config->{faildelay}; + my $zombie = $proc_state eq 'Z'; + my $ended = !$proc_state; + + unless ($success || $later || $fail || $timeout || $zombie || $ended) { + next bot; + } + + plog('INFO', "delete lock file for $prefix"); + unlink $lock_file; + + $run{bot}{$arch}{$host}{$bot} = 0; + + if ($later) { + next bot; + } + + if (!$ended && !$fail) { + plog('FAIL', "$bot timed out on $host/$arch ($seconds sec) or " . + "it's dead (status $proc_state), removing lock"); + $compildone->{$prefix}{$arch} = 0; + next bot; + } + + if ($success && !$fail) { + next bot; + } + + if (!$status) { + plog('ERR', "build bot died on $host, reschedule compilation"); + next bot; + } + + plog('FAIL', "build failed"); + create_file("$done_dir/${prefix}_$arch.fail", "$bot $host"); + mkpath($fail_dir); + + if (sget($remote, "$prefix_dir/", "$fail_dir/")) { + plog('ERR', "copying from $host:$prefix_dir/ " . + "to $fail_dir/ failed ($!)"); + $compildone->{$prefix}{$arch} = 0; + # clean the log on the compilation machine + ssh($remote, "rm -rf $prefix_dir"); + next bot; + } + + # What to do with the previously build packages? Move them to + # failure, rejected ? + # 20061220 warly move them to failure for now + + foreach my $rpm (@{$ent->{rpms}}) { + my $file = "$done_dir/${prefix}_$rpm"; + plog('DEBUG', "moving built rpm $file to $fail_dir/${prefix}_$rpm"); + link $file, "$fail_dir/${prefix}_$rpm"; + unlink $file; + } + # Should clean the queue + # Must remove the SRPM and the lock + foreach my $srpm (@{$ent->{srpms}}) { + my $file = "$todo_dir/${prefix}_$srpm"; + plog('DEBUG', "moving $file to $fail_dir/${prefix}_$srpm"); + link $file, "$fail_dir/${prefix}_$srpm"; + delete $pkg_tree{$prefix}; + unlink $file; + } + + # Notify user if build failed + # + if ($user) { + my $text = "Build of the following packages failed:\n\n"; + my $srpms = ""; + foreach my $srpm (@{$ent->{srpms}}) { + $srpms .= "$srpm "; + $text .= "- $srpm\n"; + } + + my $to = $mail->{$user} || "$user\@$config->{default_mail_domain}"; + my $fpath = "$config->{http_queue}/failure/$path/$prefix"; + $fpath =~ tr!/!!s; # Squash double slashes ... + $fpath =~ s!/!//!; # ... except for http:// + + $text .= "\nFailure details available in $fpath\n"; + sendmail($to, $config->{admin}, + "Rebuild failed on $arch for $srpms", $text, + "Ulri the scheduler bot <$config->{admin}>", 0); + } + + # clean the log on the compilation machine + ssh($remote, "rm -rf $prefix_dir"); + + } # end bot +} # end prefix + + +# +# Part 2: check queue and start new jobs if a bot is available +# + +plog('MSG', "launching new compilations"); +my %to_compile; + +# do not sort the keys to be able to ignore packages which makes iurt +# crash or just lock ulri somehow + +foreach my $prefix (sort keys %pkg_tree) { + next if $later{$prefix}; + my $ent = $pkg_tree{$prefix}; + my $path = $ent->{path}; + my $media = $ent->{media}; + my $target = $ent->{target}; + my $srpms = $ent->{srpms} or next; + my $user = $ent->{user}; + if ($user) { + $user = $mail->{$user} || $config->{packager} + } else { + $user = $config->{packager} + } + $user =~ s/([<>])/\\$1/g; + + # Local pathnames + my $done_dir = "$done/$path"; + my $todo_dir = "$todo/$path"; + + # Make sure these exist + mkpath($done_dir); + mkpath($todo_dir); + + #plog('DEBUG', "searching a bot to compile @$srpms"); + + # count noarch todos only once even if searching multiple bots + my $noarch_countflag = 0; + + # need to find a bot for each arch + foreach my $arch (keys %{$config->{bot}}) { + my $exclude; + my $noarch; + + # Skip this arch if package is building as noarch + # + next if $noarch_build{$prefix}; + + next if $compildone->{$prefix}{noarch}; + next if $compildone->{$prefix}{$arch}; + + # If all packages in a group are noarch, consider the entire group + # as noarch + # + $noarch = 1; + foreach my $srpm (@$srpms) { + if (!check_noarch("$todo_dir/${prefix}_$srpm")) { + $noarch = 0; + last; + } + } + + #plog("@$srpms is noarch") if $noarch; + + foreach my $srpm (@$srpms) { + if (!check_arch("$todo_dir/${prefix}_$srpm", $arch)) { + plog('WARN', "excluding from $arch: $srpm"); + $exclude = 1; + last; + } + } + + if ($exclude) { + create_file("$done_dir/${prefix}_$arch.excluded", + "ulri $arch excluded"); + next; + } + + if ($noarch) { + plog('DEBUG', "search any bot for @$srpms") unless $noarch_countflag; + } else { + plog('DEBUG', "search $arch bot for @$srpms"); + } + + foreach my $host (keys %{$config->{bot}{$arch}}) { + foreach my $bot (keys %{$config->{bot}{$arch}{$host}}) { + next if $run{bot}{$arch}{$host}{$bot}; + + # Enable noarch lock after the first bot snarfs the package + # + $noarch_build{$prefix} = 1 if $noarch; + + plog('INFO', "building on $host/$arch ($bot)"); + + $run{bot}{$arch}{$host}{$bot} = $prefix; + $compildone->{$prefix}{$arch} = 1; + + my $bot_conf = $config->{bot}{$arch}{$host}{$bot}; + my $remote = ssh_setup($config->{ssh_options}, + $bot_conf->{user}, $host); + + my $prefix_dir = "$bot_conf->{packages}/$path/$prefix/"; + my $status_file = "$prefix_dir/log/status.log"; + + # Copy packages to build node + # + next if ssh($remote, "mkdir -p $prefix_dir"); + my $pkgs = ""; + my $ok = 1; + foreach my $srpm (@$srpms) { + plog('NOTIFY', "Send to $host/$arch: $srpm"); + $ok &&= !sput($remote, "$todo_dir/${prefix}_$srpm", + "$prefix_dir/$srpm"); + $pkgs .= " $prefix_dir/$srpm"; + } + next unless $ok; + + # spawn remote build bot and save output on local file + # (remove status.log before building, otherwise we can have + # a install_deps_failure and reschedule even if the package + # is currently building) + # + plog('DEBUG', "remove status file"); + ssh($remote, "rm $status_file 2>/dev/null"); + + plog('INFO', "Execute build command on $host/$arch"); + + my $temp = mktemp("$config->{tmp}/ulri.tmp.$prefix.XXXXX"); + my $cmd = $bot_conf->{command}; + $cmd =~ s!__ARCH__!$arch!g; + $cmd =~ s!__DIR__!$path/$prefix!g; + $cmd =~ s!__TARGET__!$target!g; + $cmd =~ s!__PACKAGER__!$user!g; + + my $media_to_add; + if (ref $config->{media}{$target}{$media}) { + $media_to_add = join ' ', @{$config->{media}{$target}{$media}}; + } elsif (ref $config->{media}{default}{$media}) { + $media_to_add = join ' ', @{$config->{media}{default}{$media}}; + } + plog('DEBUG', "Will compile only with media $media_to_add"); + $cmd =~ s!__MEDIA__!$media_to_add!g; + + plog('DEBUG', "Build $pkgs"); + ssh($remote, "$cmd $pkgs &> $temp &"); + + # wait 10 seconds or until we have the log file + # + last if check_file_timeout($temp, 10); + + # get remote PID from log file + # + my $pid = get_pid_from_file($temp); + unlink $temp; + plog('DEBUG', "remote pid $pid"); + last unless $pid; + + # create lock file + # + my $lock_arch = $noarch ? "$arch-noarch" : $arch; + my $lock_file = "$todo_dir/${prefix}_" . + "$lock_arch.$bot.$host.$fulldate.$pid.lock"; + plog('DEBUG', "create lock $lock_file"); + create_file($lock_file, "$program_name $$", time()); + + last; + } + last if $compildone->{$prefix}{$arch}; + last if $compildone->{$prefix}{noarch}; + } + + # Count packages to compile for each architecture. Count noarch + # package only once. + # + $arch = 'noarch' if $noarch; + unless ($compildone->{$prefix}{$arch}) { + $to_compile{$arch}++ if !($noarch && $noarch_countflag); + } + $noarch_countflag = 1 if $noarch; + } +} + +plog('MSG', "Current status"); + +if (keys %build_list) { + plog('INFO', "currently building:"); + map { plog('INFO', " $_: " . join('', @{$build_list{$_}})) } keys %build_list; +} + +plog('INFO', "jobs in queue:", %to_compile ? + map { sprintf("%s(%d)", $_, $to_compile{$_}) } keys %to_compile : "none"); + + +#dump_cache(\%run); +unlink $pidfile; +exit(); + + +# +# Subroutines +# + +sub get_pid_from_file { + my $file = shift; + + my $pid; + open my $FILE, $file || die "FATAL: can't open $file"; + local $_; + while (<$FILE>) { ($pid) = /PID=(\d+)/ } + + $pid; +} + +sub create_file { + my $file = shift; + my @contents = @_; + + open my $FILE, ">$file" or die "FATAL: can't open $file for writing"; + print $FILE "@contents"; +} + +sub read_line { + my $file = shift; + + open my $FILE, "<$file" or die "FATAL: can't open $file for reading"; + my $contents = <$FILE>; + + $contents; +} + +sub check_file_timeout { + my $file = shift; + my $time = shift; + + my $i = 0; + while ($i < $time && (!-f $file || -z $file)) { sleep 1; $i++ } + + $i == $time; +} + +__END__ + +# ulri ends here + +Discussion +---------- + +20060802 (Warly) + +* I prefer creating a separate scheduler, so that it can eventually call + other bots. +* bots should be able to take packages by themselves. +* Iurt will perform several checks, they have to be standard and usable + by the maintainer, the results must be put in a visible directory or path +* We can put packages either in a dir or to prefix all files with the date + and uploader. Having all files in a dir will make the listing simpler. + Prefixing the files could be problematic if we base the rpm name and + version parsing on the filename. +* ulri knows the prefix, he could ask iurt to put the packages in a dir + with the same prefix. + +20060806 (Warly) + +* All the packages are put in done, then the final youri is run to put them + in queue/ + +20061104 (claudio) + +* Instead if having configuration defaults for our environment and using + ulri with the defaults, it would be nicer to have minimalistic/generic + defaults and install a configuration file in kenobi +* Ulri's configuration file could be renamed to .ulri.conf instead of + .upload.conf. ==> ok, it's also used by emi + |