#!/usr/bin/perl # # Copyright (C) 2005 Mandrakesoft # Copyright (C) 2005,2006 Mandriva # # Author: Florent Villard # # 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 # # - use a cache (rpmctl cache for example) to find maintainer # - add icecream compilation support # - add a --set option to compile a set of packages # use strict; use Hdlist; use Data::Dumper; use URPM; use File::NCopy qw(copy); use MIME::Words qw(encode_mimewords); # I did not manage to make locks work over the network #use File::lockf; use Mkcd::Commandline qw(parseCommandLine usage); use Filesys::Df qw(df); my $program_name = 'iurt2'; # sessing parameters my $arg = @ARGV; my (@params, %run); $run{todo} = [ ]; @params = ( # [ "one letter option", "long name option", "number of args (-X means ´at least X´)", "help text", "function to call", "log info"] [ "", "$program_name", 0, "[--cache] [--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] [--unionfs] [--upload [--markrelease] [--source]] [--dir] [--help foo?] [--log filename] [--unionfs] {--config_help | --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.", sub { $arg or usage($program_name, \@params) }, "" ], [ "", "distro", 1, "", "Set the distribution", sub { ($run{distro}) = @_; 1 }, "Setting the distribution" ], [ "a", "arch", 1, "", "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" ], [ "c", "chroot", 0, "", "Check chroot and update it if needed", sub { $run{chroot} = 1 }, "Activating chroot updating" ], [ "", "clean-all", 0, "", "Clean all remaining chroots for all the users", sub { $run{clean_all} = 1 }, "Activating clean chroot flag" ], [ "", "clean", -1, " ... ", "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, " ", "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" ], [ "", "debug", 0, "", "Activate debug mode", sub { $run{debug} = 1 }, "Activating debug mode" ], [ "u", "unionfs", 0, "", "Activate unionfs mode", sub { $run{unionfs} = 1 }, "Activating unionfs mode" ], [ "l", "log", 1, "", "Log file.", sub { $run{log} = pop @_; open $run{LOG}, ">$run{log}" or die "unable to open $run{log}\n"; print *{$run{LOG}}, "command line: @ARGV\n"; 1 }, "Log file" ], [ "m", "media", -1, "", "Media to rebuild", sub { ($run{media}) = @_; 1 }, "Adding a media to rebuild" ], [ "r", "rebuild", -2, " ... ", "Rebuild the packages, e.g. $program_name -r cooker x86_64 /home/foo/rpm/SRPMS/foo-2.3-12mdk.src.rpm", sub { $run{rebuild} = 1; $run{distro} = shift @_; $run{my_arch} = shift @_; foreach (@_) { my ($path, $srpm); if (m,(.*/)([^/]*.src.rpm)$, && -f $_) { ($path, $srpm) = ( $1, $2 ) } elsif (m,([^/]*.src.rpm)$, && -f $_) { ($path, $srpm) = ( './', $1 ) } else { die "FATAL iurt: $_ does not seems to be a SRPM\n" } my $hdr = rpm2header($_); if (check_arch($hdr)) { print {$run{LOG}} "iurt: force build for $2 (from $1)\n"; push @{$run{todo}}, [ $path, $srpm ] } else { print {$run{LOG}} "ERROR iurt: $_ could not be build on $run{my_arch}, ignored.\n" } } 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" ], [ "", "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, "", "Use the current system urpmi configuration", sub { $run{use_system_distrib} = shift; 1 }, "Setting system distrib for urpmi configuration" ], [ "v", "verbose", 1, "", "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, "", "Perform rpm -b (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" ], ); open(my $LOG, ">&STDERR"); $run{LOG} = $LOG; my $todo = parseCommandLine($program_name, \@ARGV, \@params); @ARGV and usage($program_name, \@params, "@ARGV, too many arguments"); foreach my $t (@$todo) { print {$run{LOG}} "$program_name: $t->[2]\n" if $run{verbose} > 5; &{$t->[0]}(@{$t->[1]}) or print {$run{LOG}} "ERROR: $t->[2]\n"; } $run{distro_tag} = $run{distro}; $run{distro_tag} =~ s,/,-,g; my $real_arch = `uname -m`; chomp $real_arch; my %arch_comp = ( 'i586' => { 'i386' => 1, 'i486' => 1, 'i586' => 1 }, 'i686' => { 'i386' => 1, 'i486' => 1, 'i586' => 1, 'i686' => 1 }, 'x86_64' => { 'i386' => 1, 'i486' => 1, 'i586' => 1, 'i686' => 1, 'x86_64' => 1 }, 'ppc' => { 'ppc' => 1 }, 'ppc64' => { 'ppc' => 1, 'ppc64' => 1 }, ); my $HOME = $ENV{HOME}; my $configfile = "$HOME/.iurt.$run{distro_tag}.conf"; print {$run{LOG}} "iurt: loading config file $configfile\n" if $run{verbose} > 1; my $config; if (-f $configfile) { $config = do $configfile or die "FATAL iurt: syntax error in $configfile"; } else { $config = {} } my $urpmi_options = "-v --no-verify-rpm --nolock --auto --ignoresize"; if ($run{use_system_distrib}) { $config->{basesystem_media_root} ||= $run{use_system_distrib} } else { $urpmi_options .= " --use-distrib $config->{repository}/$run{distro}/$run{my_arch}" } 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' }, 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 }, 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 => '' }, 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 }, 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' }, repository => { desc => 'Prefix of the repositories', default => '/mnt/BIG/dis/' }, 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 @{$run{todo}})"); if (!$arch_comp{$real_arch}{$run{my_arch}}) { die "FATAL iurt: could not compile $run{my_arch} binaries on a $real_arch" } foreach my $k (keys %config_usage) { ref $config_usage{$k}{default} eq 'CODE' and next; $config->{$k} ||= $run{config}{$k} || $config_usage{$k}{default} } 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 } $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; } -d $config->{upload} or usage($program_name, \@params, "$config->{upload} does not exist"); # cache file name is needed early to remove the manual lock file if the lock mechanism does not work my $cachefile = "$config->{cache_home}/iurt.$run{distro_tag}.$run{my_arch}.$run{media}.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}.$run{media}"; check_pid(\%run) } my $debug_tag = '_debug' if $run{debug}; my $chroot_name = "chroot_$run{distro_tag}$debug_tag"; my $chroot = "$config->{local_home}/$chroot_name"; my $chroot_tar = "$chroot.$run{my_arch}.tar.gz"; if ($run{chroot}) { check_chroot($chroot, $chroot_tar, \%run) } my $cache; my $clear_cache = 1; if (-f $cachefile && $run{use_cache}) { print {$run{LOG}} "iurt: loading cache file $cachefile\n" if $run{verbose} > 1; $cache = do $cachefile or print "FATAL iurt: could not load cache $cachefile ($!)\n"; 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" } } print {$run{LOG}} "iurt: loading alternate cache file $cachefile\n"; $cache = do $cachefile or print "FATAL iurt: could not load cache $cachefile ($!)\n" } $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, \%run, @{$run{extra_dir}}) if $run{extra}; dump_cache(\%run); print {$run{LOG}} "iurt: will try to compile $to_compile packages\n" if $run{verbose} > 1; my ($fulldate, $daydate) = get_date(); if ($run{use_cache}) { $run{run} = $cache->{run}; $cache->{run}++ } else { $run{run} = "0.$fulldate" } print {$run{LOG}} "iurt: using $run{run} as chroot extension\n" if $run{verbose} > 4; $run{user} = $ENV{SUDO_USER} || $ENV{USER}; $run{uid} = getpwnam $run{user}; print {$run{LOG}} "iurt: using local user $run{user}, id $run{uid}\n" if $run{verbose} > 3; my $luser = $run{user} || 'builder'; my $unionfs_dir; if ($run{unionfs}) { # FIXME need to grep /proc/modules not ot try to load it if already loaded open my $modules, '/proc/modules'; my $ok; while (my $m = <$modules>) { if ($m =~ /unionfs/) { $ok = 1; last } } if (!$ok) { print {$run{LOG}} "iurt: adding unionfs module\n" if $run{verbose} > 0; system("sudo /sbin/depmod -a"); system("sudo /sbin/modprobe -f unionfs"); } $unionfs_dir = "$config->{local_home}/iurt_unionfs$debug_tag/"; remove_chroot(\%run, $unionfs_dir, \&clean_all_unionfs); $unionfs_dir = "$unionfs_dir/$run{user}/"; -d $unionfs_dir or mkdir $unionfs_dir } my (%done, $wait_limit, $done); $run{done} = \%done; my $home = $config->{local_home}; my $union_id = 1; my $unionfs_tmp = $run{unionfs}; my $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}"; # now exit if there is nothing to do and it was just a cleaning pass if (!@{$run{todo}} && !$run{debug} && !$run{shell} && !$run{rebuild}) { print {$run{LOG}} "iurt: nothing to do\n"; unlink "$run{pidfile_home}/$run{pidfile}" if $run{pidfile}; exit } my $df = df $home; if ($df->{per} == 100) { die "FATAL iurt: not enough space on the filesystem, only $df->{bavail}KB on $home, full at $df->{per}%" } if ($run{shell}) { my ($unionfs_tmp, $chroot_tmp) = create_temp_chroot(\%run, $cache, $unionfs_tmp, $unionfs_dir, $union_id); add_local_user($chroot_tmp, \%run, $luser, $run{uid}); add_sudo(\%run, $config, $chroot_tmp, $luser); if ($run{shell}) { print {$run{LOG}} "iurt: dumping to a chrooted shell into $chroot_tmp\n"; exec "sudo chroot $chroot_tmp /bin/su $luser -c bash" } } $config->{local_upload} ||= $config->{local_home}; my $local_spool = "$config->{local_upload}/iurt/$run{distro_tag}/$run{my_arch}"; if (!-d $local_spool) { print {$run{LOG}} "iurt: creating local spool $local_spool\n" if $run{verbose} > 4; -d "$config->{local_upload}/iurt" or mkdir "$config->{local_upload}/iurt"; my $d = "$config->{local_upload}/iurt/$run{distro_tag}"; if (!-d $d) { mkdir $d or die "FATAL iurt: could not create local spool dir $d ($!)" } if (!-d $local_spool) { mkdir $local_spool; mkdir "$local_spool/log" } } # 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}/log/"); } print {$run{LOG}} "iurt: try to dump rpm macros to $chroot/home/builder/.rpmmacros\n" if $run{verbose} > 3; if (!dump_rpmmacros("$chroot/home/builder/.rpmmacros")) { print "ERROR iurt: could not dump rpm macros to $chroot/home/builder/.rpmmacros, trying to rebuild the chroot"; check_chroot($chroot, $chroot_tar, \%run); dump_rpmmacros("$chroot/home/builder/.rpmmacros") or die "FATAL iurt: could not dump rpm macros to $chroot/home/builder/.rpmmacros" } my $s = sub { if ($run{main}) { print {$run{LOG}} "iurt: dumping cache...\n"; dump_cache(\%run); $Data::Dumper::Indent = 0; $Data::Dumper::Terse = 1; print {$run{LOG}} "Running environment:\n", Data::Dumper->Dump([\%run]), "\n\n"; print {$run{LOG}} "Configuration:\n",Data::Dumper->Dump([$config]),"\n\n"; } exit }; $SIG{TERM} = $s; $SIG{INT} = $s; $run{main} = 1; foreach (my $i ; $i < @{$run{todo}}; $i++) { my ($dir, $srpm) = @{$run{todo}[$i]}; $done{$srpm} and next; $done{$srpm} = 1; check_version($srpm) or next; if ($run{debug}) { $run{debug}++ == 2 and exit } $done++; mkdir "$local_spool/log/$srpm"; print {$run{LOG}} "iurt: packages $srpm [$done/$to_compile]\n"; # FIXME unfortunately urpmi stalls quite often my $retry = 0; retry: my $match = "urpmi $urpmi_options --root $chroot_tmp"; if (!clean_process($match, $run{verbose})) { dump_cache(\%run); die "FATAL iurt: Could not have urpmi working !" } my ($unionfs_tmp, $chroot_tmp) = create_temp_chroot(\%run, $cache, $unionfs_tmp, $unionfs_dir, $union_id, $srpm); my ($srpm_name) = $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm$/ or next; my ($maintainer, $cc); if (!$run{warn}) { $maintainer = `rpmmon -s -p $srpm_name`; $cc = "$maint{$srpm}";#, maintainers\@mandriva.com"; chomp $maintainer; if (!$maintainer || $maintainer eq 'NOT_FOUND') { $maintainer = $cc; #$cc = 'maintainers@mandriva.com' } } #($maintainer, $cc) = ($config->{admin},''); print {$run{LOG}} "iurt: adding local user to the chroot...\n" if $run{verbose}; add_local_user($chroot_tmp, \%run, $luser, $run{uid}); # recreate a new srpm for buildarch condition in the spec file print {$run{LOG}} "iurt: copying $srpm to $chroot_tmp\n" if $run{verbose} > 1; perform_command("sudo cp $dir/$srpm $chroot_tmp/home/$luser/rpm/SRPMS/", \%run, $config, mail => $config->{admin}, error => "[REBUILD] cannot copy $srpm to $chroot_tmp", debug_mail => $run{debug}, hash => "copy_$srpm") or next; my %opt = ( mail => $config->{admin}, error => "[REBUILD] cannot install $srpm in $chroot_tmp", debug_mail => $run{debug}, hash => "install_$srpm", retry => $retry, callback => sub { my ($opt, $output) = @_; print {$run{LOG}} "calling callback for $opt->{hash}\n" if $run{debug}; if ($output =~ /user $luser does not exist/) { print {$run{LOG}} "WARNING iurt: chroot seems corrupted...\n" if $run{verbose} > 1; $opt->{error} = "[CHROOT] chroot is corrupted"; $opt->{retry} = 1 if !$opt->{retry}; return } 1 } ); print {$run{LOG}} "iurt: recreating src.rpm...\n" if $run{verbose}; if (!perform_command(qq{sudo chroot $chroot_tmp su $luser -c "rpm -i /home/$luser/rpm/SRPMS/$srpm"}, \%run, $config, %opt)) { print {$run{LOG}} "ERROR iurt: chrooting failed (retry $opt{retry}\n" if $run{debug}; if ($opt{retry}) { $retry = 1; check_chroot($chroot, $chroot_tar, \%run); goto retry } die; next } perform_command(qq{sudo chroot $chroot_tmp su $luser -c "rpm --nodeps -bs /home/$luser/rpm/SPECS/*.spec"}, \%run, $config, mail => $config->{admin}, error => "[REBUILD] cannot create $srpm in $chroot_tmp", debug_mail => $run{debug}, hash => "create_$srpm") or next; print {$run{LOG}} "iurt: installing build dependencies of $srpm...\n" if $run{verbose}; if (!perform_command("sudo urpmi $urpmi_options --root $chroot_tmp $chroot_tmp/home/$luser/rpm/SRPMS/$srpm", \%run, $config, mail => $config->{admin}, error => "[REBUILD] install of build dependencies of $srpm failed on $run{my_arch}", hash => "install_deps_$srpm", timeout => 600, srpm => $srpm, freq => 1, #cc => $cc, retry => 10, debug_mail => $run{debug}, error_regexp => 'cannot be installed', wait_regexp => 'database locked', wait_callback => sub { print {$run{LOG}} "WARNING iurt: urpmi database locked, waiting...\n" if $run{debug}; sleep 30; $wait_limit++; if ($wait_limit > 10) { $wait_limit = 0; system(qq{sudo pkill -9 urpmi &>/dev/null}) } }, log => "$local_spool/log/$srpm/", callback => sub { my ($opt, $output) = @_; print {$run{LOG}} "calling callback for $opt->{hash}\n" if $run{debug}; my @missing_deps = $output =~ /\(due to unsatisfied ([^[ ]*)(?: (.*)|\[(.*)\])?\)/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; while (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 $other_maint = `rpmmon -p "$p"`; chomp $other_maint; print {$run{LOG}} "missing dep: $missing_deps ($other_maint)\n"; if ($other_maint && $other_maint ne 'NOT_FOUND') { $opt->{mail} = "$maintainer, $other_maint"; $opt->{error} = "[MISSING] $missing_deps, needed to build $srpm, is not available on $run{my_arch}"; } # remember what is needed, and do not try to recompile until it is available push @{$cache->{needed}{$srpm}}, [ $missing_deps, $version, $other_maint || $maintainer ]; } 1 }, )) { $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 # running rpm -qa several time seems to fix the problem foreach (1 .. 3) { system("sudo chroot $chroot_tmp rpm -qa &> /dev/null") } perform_command("sudo chroot $chroot_tmp rpm -qa", \%run, $config, hash => "rpm_qa_$srpm", timeout => 60, debug_mail => $run{debug}, log => "$local_spool/log/$srpm/"); # or next; As this failed quite often, do not stop print {$run{LOG}} "Compiling $srpm\n" if $run{verbose}; my $command = "rpm --rebuild /home/$luser/rpm/SRPMS/$srpm"; if ($run{stop}) { add_sudo(\%run, $config, $chroot_tmp, $luser); $command = "rpm -b$run{stop} /home/$luser/rpm/SPECS/*.spec" } if (!perform_command(qq{TMP=/home/$luser/tmp/ sudo chroot $chroot_tmp /bin/su $luser -c "$command"}, \%run, $config, mail => $maintainer, error => "[REBUILD] $srpm from $run{distro_tag} does not build correctly on $run{my_arch}", hash => "build_$srpm", timeout => 18000, srpm => $srpm, debug_mail => $run{debug}, cc => $cc, log => "$local_spool/log/$srpm/", error_regexp => 'rror.*ailed|Bad exit status|RPM build error', callback => sub { my ($opt, $output) = @_; if ($run{stop}) { print {$run{LOG}} "iurt: dumping to a chrooted shell into $chroot_tmp\n"; exec "sudo chroot $chroot_tmp /bin/su $luser -c bash" } print {$run{LOG}} "iurt: calling callback for $opt->{hash}\n" if $run{debug}; if ($unionfs_tmp && $output =~ /no space left on device/i) { print {$run{LOG}} "ERROR iurt: running out of space to compile $srpm in unionfs mode, will recompile it in normal mode\n"; $cache->{no_unionfs}{$srpm} = 1; return 1 } elsif ($unionfs_tmp && $output =~ m,$home,) { print {$run{LOG}} "ERROR iurt: seems like building $srpm needs to access /proc/self/exe, which is broken with unionfs, will try to recompile it in non unionfs mode\n"; $cache->{no_unionfs}{$srpm} = 1; return 1 } 1 }, freq => 1) && !glob "$chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm") { # 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 (!$unionfs_tmp) { $run{status}{$srpm} = 'build_failure'; $cache->{failure}{$srpm} = 1 } elsif (!$cache->{no_unionfs}{$srpm}) { $run{status}{$srpm} = 'build_failure'; $cache->{failure}{$srpm} = 1 } else { goto retry } next } # do some cleaning if the compilation is successful delete $cache->{needed}{$srpm} if defined $cache->{needed}{$srpm}; if (!perform_command("sudo urpmi $urpmi_options --root $chroot_tmp $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm", \%run, $config, mail => $maintainer, error => "[REBUILD] binaries packages generated from $srpm do not install correctly", hash => "binary_test_$srpm", srpm => $srpm, timeout => 300, debug_mail => $run{debug}, freq => 1, error_regexp => 'unable to access', log => "$local_spool/log/$srpm/")) { $cache->{failure}{$srpm} = 1; $run{status}{$srpm} = 'binary_test_failure'; next } if ($run{debug}) { print {$run{LOG}} "iurt: debug mode, skip other packages\n"; exit } else { print {$run{LOG}} "iurt: build successful, copying packages to $local_spool.\n"; $run{status}{$srpm} = 'ok'; system("cp $chroot_tmp/home/$luser/rpm/RPMS/*/*.rpm $local_spool &>/dev/null") and print {$run{LOG}} "ERROR: could not copy rpm files from $chroot_tmp/home/$luser/rpm/RPMS/ to $local_spool ($!)\n"; if ($run{copy_srpm}) { system("cp $chroot_tmp/home/$luser/rpm/SRPMS/$srpm $local_spool &>/dev/null") and print {$run{LOG}} "ERROR: could not copy $srpm from $chroot_tmp/home/$luser/rpm/SRPMS/ to $local_spool ($!)\n"; } process_queue($config, \%run, \@wrong_rpm, 1) } # dymp_cache each time so that concurrent process can get updated dump_cache(\%run) if $run{concurrent_run} } print {$run{LOG}} "iurt: reprocess generated packages queue\n" if $run{verbose}; process_queue($config, \%run, \@wrong_rpm); dump_cache(\%run); print {$run{LOG}} "ERROR iurt: RPM with a wrong SRPM name\n" if @wrong_rpm; if (open my $file, ">$local_spool/log/wrong_srpm_names.log") { foreach (@wrong_rpm) { print $file "$_->[1] -> $_->[0] (", $cache->{rpm_srpm}{$_->[1]},")\n"; } close $file } if (open my $file, ">$local_spool/log/status.$run{media}.log") { foreach my $srpm (keys %{$run{status}}) { print $file "$srpm: "; if ($run{status}{$srpm}) { print $file $run{status}{$srpm} } else { print $file "unknown" } print $file "\n" } close $file } 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}/log/"); } # one last try to clean print {$run{LOG}} "iurt: try to clean remaining unionfs\n" if $run{verbose}; if ($run{unionfs}) { remove_chroot(\%run, $unionfs_dir, \&clean_all_unionfs) } unlink "$run{pidfile_home}/$run{pidfile}" if $run{pidfile}; exit; sub config_usage { 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"; exit } sub clean_all_unionfs { my ($unionfs_dir) = @_; print {$run{LOG}} "Cleaning old unionfs remaining dir in $unionfs_dir\n" if $run{verbose} > 1; opendir my $dir, $unionfs_dir or die "FATAL iurt: could not open $unionfs_dir ($!)"; foreach (readdir $dir) { /unionfs\.((?:0\.)?\d+)\.(\d+)$/ or next; clean_unionfs($unionfs_dir, $1, $2); } closedir $dir } sub clean_all_chroot_tmp { my ($chroot_dir, $prefix) = @_; print {$run{LOG}} "Cleaning old chroot remaining dir in $chroot_dir\n" if $run{verbose}; opendir my $dir, $chroot_dir or die "FATAL iurt: could not open $chroot_dir ($!)"; foreach (readdir $dir) { /$prefix.*$/ or next; clean_chroot_tmp($chroot_dir, $_); } closedir $dir } sub clean_chroot { my ($chroot, $run, $only_clean) = @_; if (-d $chroot) { system("sudo umount $chroot/proc &> /dev/null"); perform_command("sudo rm -rf $chroot", $run, $config, mail => $config->{admin}, error => "[REBUILD] Deleting of old chroot $chroot failed", hash => 'chroot_deletion', debug_mail => $run->{debug}, die => 1); } return 1 if $only_clean; mkdir $chroot; perform_command("pushd $chroot && sudo tar xvf $chroot_tar", $run, $config, mail => $config->{admin}, error => "[REBUILD] creating the initial chroot $chroot failed", hash => 'chroot_init', debug_mail => $run->{debug}, die => 1); dump_rpmmacros("$chroot/home/builder/.rpmmacros") or return; system("sudo mount none -t proc $chroot/proc &>/dev/null") and return; 1 } sub clean_process { my ($match, $verbose) = @_; return clean($match, "pgrep -u root -f", "sudo pkill -9 -u root -f", $verbose); } sub clean_mnt { my ($mount_point, $verbose) = @_; return clean($mount_point, "/sbin/fuser", "sudo /sbin/fuser -k", $verbose); } sub clean { my ($var, $cmd, $kill_cmd, $verbose) = @_; my $ps; my $i; while ($ps = `$cmd "$var"`) { system(qq{$kill_cmd "$var" &>/dev/null}); sleep 1; $ps =~ s/\n/,/g; print {$run{LOG}} "Trying to remove previous blocked processes for $var ($ps)\n" if $verbose > 1; return 0 if $i++ > 10 } 1 } sub clean_unionfs { my ($unionfs_dir, $run, $union_id) = @_; if (system("sudo umount $unionfs_dir/unionfs.$run.$union_id/proc &>/dev/null") && $run{verbose} > 1) { print {$run{LOG}} "ERROR iurt: could not umount /proc in $unionfs_dir/unionfs.$run.$union_id\n" } print {$run{LOG}} "Cleaning $unionfs_dir/unionfs.$run.$union_id\n" if $run{verbose} > 1; my $nok = 1; while ($nok) { foreach my $t ("unionfs",'tmpfs') { # unfortunately quite oftem the unionfs is busy and could not be unmounted my $d = "$unionfs_dir/$t.$run.$union_id"; my $last; while (check_mounted($d, $t)) { system("sudo /sbin/fuser -k $d &> /dev/null"); print {$run{LOG}} "iurt: umounting $d\n" if $run{verbose} > 2; if (system(qq{sudo umount $d &> /dev/null})) { print {$run{LOG}} "WARNING iurt: could not umount $d ($!)\n" if $run{verbose} > 1; $union_id++; $last = 1; last } } $last and last; print {$run{LOG}} "iurt: removing $d\n" if $run{verbose} > 1; system(qq{sudo rm -rf $d}); $nok = 0 } } $union_id } sub clean_chroot_tmp { my ($chroot_dir, $dir) = @_; my $d = "$chroot_dir/$dir"; if (system("sudo umount $d/proc &>/dev/null") && $run{verbose} > 1) { print {$run{LOG}} "ERROR iurt: could not umount /proc in $d/\n" } print {$run{LOG}} "iurt: cleaning $d\n" if $run{verbose}; system("sudo /sbin/fuser -k $d &> /dev/null"); print {$run{LOG}} "iurt: removing $d\n" if $run{verbose}; system(qq{sudo rm -rf $d}); } sub check_mounted { my ($mount_point, $type) = @_; open my $mount, '/proc/mounts' or die 'FATAL iurt: could not open /proc/mounts'; $mount_point =~ s,//+,/,g; while (<$mount>) { return 1 if /^\w+ $mount_point $type / } 0 } sub check_needed { my ($srpm) = @_; if (!defined $cache->{needed}{$srpm} && !ref $cache->{needed}{$srpm}) { return 1 } my @n; my $ok = 1; foreach my $t (@{$cache->{needed}{$srpm}}) { my ($name, $version, $maint) = @$t; my ($p_version) = $provides{$name}; if ($p_version) { next if $version == $p_version; next if URPM::ranges_overlap($version, $p_version) } $ok = 0; push @n, [ $name, $version ]; my $v ||= $version; print {$run{LOG}} "ERROR iurt: $srpm needs $name $v to be compiled.\n"; # try to recompile it once in a while return 1 if ! $cache->{warning}{"install_deps_$srpm"}{$maint}++ % 72 } delete $cache->{needed}{$srpm}; $cache->{needed}{$srpm} = \@n if @n; $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}"; opendir my $rpmdir, $dir or return; foreach my $rpm (readdir $rpmdir) { my ($rarch, $srpm) = update_srpm($dir, $rpm, $wrong_rpm); $rarch or next; print {$run{LOG}} "iurt: $rpm\n"; 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 $ok = copy "$dir/$rpm", "$config->{upload}/$config->{extra_subdir}/RPMS/"; # try to keep the opportunity to prevent disk full if (!$ok){ print {$run{LOG}} "ERROR process_queue: cannot copy $dir/$rpm to $config->{upload}/$config->{extra_subdir}/RPMS/ ($!)\n"; next } } if ($run->{upload_source}) { } unlink "$dir/$rpm"; $cache->{queue}{$srpm} = 1 } closedir $rpmdir; } sub update_srpm { my ($dir, $rpm, $wrong_rpm) = @_; my ($arch) = $rpm =~ /([^\.]+)\.rpm$/ or return 0; my $srpm = $cache->{rpm_srpm}{$rpm}; if (!$srpm) { my $hdr = rpm2header("$dir/$rpm"); $hdr or return 0; $srpm = $hdr->queryformat("%{SOURCERPM}"); $cache->{rpm_srpm}{$rpm} = $srpm } $srpm = fix_srpm_name($srpm, $rpm, $wrong_rpm); $arch, $srpm } sub dump_cache { my ($run) = @_; my $filename = $run->{cachefile}; my $cache = $run->{cache}; # 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}) { print {$run{log}} "iurt: merging cache"; my $old_cache; if (-f $filename) { print {$run{LOG}} "iurt: loading cache file $cachefile\n" if $run{verbose} > 1; $old_cache = do $cachefile; foreach my $k ('rpm_srpm', 'failure', 'no_unionfs', 'queue', 'needed', 'warning') { 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 { print {$run{LOG}} "WARNING iurt: locking the cache file $cachefile failed (status $status $!), try to lock manually\n"; if (-f "$filename.lock") { print {$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" } } } sub sendmail { my ($to, $cc, $subject, $text, $from, $debug) = @_; do { print "Cannot find sender-email-address [$to]\n"; return } unless defined($to); $from ||= $config->{packager}; my $MAIL; if (!$debug) { open $MAIL, "| /usr/sbin/sendmail -t" or return } else { open $MAIL, ">&STDOUT" or return } my $sender = encode_mimewords($to); my $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) } sub check_arch { my ($hdr) = @_; my (@exclusive_arch) = $hdr->queryformat('%{EXCLUSIVEARCH}'); return if ! grep { $_ eq $run{my_arch} || $_ eq '(none)' } @exclusive_arch; my (@exclude_arch) = $hdr->queryformat('%{EXCLUDEARCH}'); return if grep { $_ eq $run{my_arch} } @exclude_arch; 1 } sub check_version { my ($srpm) = @_; my ($srpm_name) = $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm/; if (URPM::ranges_overlap("= $srpm",">= $srpm_version{$srpm_name}")) { $srpm_version{$srpm_name} = $srpm; return 1 } 0 } sub fix_srpm_name { my ($srpm, $rpm, $wrong_rpm) = @_; my $old_srpm = $srpm; if ($srpm =~ s/^lib64/lib/){ push @$wrong_rpm, [ $old_srpm, $rpm ]; $cache->{rpm_srpm}{$rpm} = $srpm } $srpm } sub perform_command { my ($command, $run, $config, %opt) = @_; $opt{timeout} ||= 300; $opt{freq} ||= 24; print {$run{LOG}} "Timeout $opt{timeout}\n" if $run{verbose} > 2; # from alarm perldoc my $output; my ($kill, $pipe); if ($opt{debug}) { print "Would have rum $command with a timeout of $opt{timeout}\n"; return 1 } local $SIG{PIPE} = sub { print "Broken pipe!\n"; $pipe = 1 }; my $logfile = "$opt{log}/$opt{hash}.$run->{run}.log"; my $retry = $opt{retry} || 1; my $call_ret = 1; my ($err, $pid); while ($retry) { if ($opt{log}) { my $parent_pid = $$; $pid = fork; my $tot_time; if (!$pid) { print {$run{LOG}} "Forking to monitor log size\n" if $run{verbose} > 2; $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) { print {$run{LOG}} "WARNING: killing current command because of log size exceeding limit ($stat[7] > $config->{log_size_limit})\n"; kill 14, "-$parent_pid"; exit } my $df = df $opt{log} ; if ($df->{per} == 100) { print {$run{LOG}} "WARNING: killing current command because running out of disk space (only $df->{bavail}KB left)\n"; 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}; print {$run{LOG}} "$command\n" if $run{verbose} > 2; 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 = $?; # kill pid watching log file size if ($pid) { use POSIX ":sys_wait_h"; kill 14, $pid; waitpid(-1, WNOHANG) } if ($@) { # timed out die "FATAL iurt: unexpected signal" unless $@ eq "alarm\n"; # propagate unexpected errors } # 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> } if (ref $opt{callback}) { $call_ret = $opt{callback}(\%opt, $output); $call_ret == -1 and return 1 } if ($kill) { $output = "Command has been killed after $opt{timeout} seconds: $command\n$output"; my ($cmd_to_kill) = $command =~ /sudo(?: chroot \S+)? (.*)/; clean_process($cmd_to_kill, $run{verbose}) } elsif ($pipe) { $output = "Command receives a broken pipe: $command\n$output"; sendmail($config->{admin}, '' , "$opt{hash} on $run->{my_arch} for $run->{media}: broken pipe", "$output", 0, 0, $opt{debug_mail}); } else { $output = "Command failed: $command\n$output" } if ($opt{wait_regexp} && $output =~ /$opt{wait_regexp}/) { $opt{wait_callback}(\%opt, $output) if ref $opt{wait_callback}; print {$run->{LOG}} "ERROR iurt: $opt{wait_regexp} !\n"; sendmail($config->{admin}, '' , "$opt{hash} on $run->{my_arch} for $run->{media}: could not proceed", "$opt{wait_regexp}\n\n$output", 0, 0, $opt{debug_mail}); if ($opt{die}) { dump_cache($run); die "FATAL iurt: $opt{error}." } $retry--; return if !$retry } else { $retry = 0 } } if (!$call_ret || $kill || $err || $opt{error_regexp} && $output =~ /$opt{error_regexp}/) { if ($opt{log} && $config->{log_url}) { $output = qq|See $config->{log_url}/$run{distro_tag}/$run{my_arch}/log/$opt{srpm}/\n\n$output| } 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} , $output, 0, 0, $opt{debug_mail}); } elsif ($config->{admin}) { sendmail($config->{admin}, '' , $opt{error}, $output, 0, 0, $opt{debug_mail}); } } $cache->{warning}{$opt{hash}}{$opt{mail}}++; print {$run->{LOG}} "\n$output\n"; if ($opt{die}) { dump_cache($run); die "FATAL iurt: $opt{error}." } return 0 } 1 } sub kill_for_good { my ($pid) = @_; kill 14, $pid; sleep 2; if (getpgrp $pid != -1) { kill 15, $pid; sleep 2; if (getpgrp $pid != -1) { kill 9, $pid } } } sub check_chroot { my ($chroot, $chroot_tar, $run) = @_; print {$run{LOG}} "iurt: checking basesystem tar\n" if $run{verbose}; system(qq{sudo pkill -9 -u root -f "urpmi $urpmi_options --root $chroot" &> /dev/null}); clean_chroot($chroot, $run, 1) or die "FATAL iurt: Could no prepare initial chroot"; perform_command("sudo $config->{install_chroot_binary} $config->{basesystem_media_root} $config->{basesystem_media_root}/media/$config->{basesystem_media} $chroot_tar $chroot 501 basesystem tar rpm-build", $run, $config, mail => $config->{admin}, error => "[REBUILD] Creating the inital chroot for $run->{distro_tag} on $run->{my_arch} failed", hash => 'chroot_inititialization', timeout => 600, debug_mail => $run->{debug}, die => 1); } sub dump_rpmmacros { my ($file) = @_; my $f; if (!open $f, qq{| sudo sh -c "cat > $file"}) { print {$run{LOG}} "ERROR iurt: could not open $file ($!)\n"; return 0 } print $f qq{\%_topdir \%(echo \$HOME)/rpm \%_tmppath \%(echo \$HOME)/rpm/tmp/ \%distribution $config->{distribution} \%vendor $config->{vendor} \%packager $config->{packager}}; close $f; -f $file or return 0; 1 } sub check_pid { my ($run) = @_; my $hostname = `hostname`; chomp $hostname; my $pidfile = $run->{pidfile}; my $lockfile = "$run->{pidfile_home}/$pidfile.$hostname.pid.lock"; print {$run->{LOG}} "iurt: trying to lock $lockfile\n"; 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 { print {$run->{LOG}} "ERROR iurt: could not lock pid file (status $status $!)\n"; if (! -f "$lockfile.2") { print {$run->{LOG}} "iurt: using $lockfile.2 as lock file\n"; open my $lock2, ">$lockfile.2" or die "FATAL iurt: could not open lock file $lockfile.2"; print $lock2 $$; close $lock2; } else { # protection mecchanism to remove dead lock files unlink "$lockfile.2"; die "FATAL iurt: could not lock pid file (status $status $!)\n"; } } if (!$run->{concurrent_run}) { 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 = "iurt: an other iurt is running for $run->{my_arch} on $pid_host, pid $pid, since $diff seconds"; if ($diff < 36000) { print {$run->{LOG}} "$msg\n"; exit } else { print {$run->{LOG}} "$msg, ignoring it\n" } } } $run->{pidfile} .= ".pid"; } $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) { print {$run->{LOG}} "ERROR iurt: invalid pidfile ($pid), should be "; unlink $pidfile } if ($pid && getpgrp $pid != -1) { my $time = $stat[9]; if ($time < time - 36000) { print {$run->{LOG}} "iurt: an other iurt pid $pid is running for a very long time, killing it\n"; my $i; while ($i < 5 && getpgrp $pid != -1) { kill_for_good($pid); $i++; sleep 1 } } else { print {$run->{LOG}} "iurt: an other iurt is running for $run->{my_arch}, pid $pid, since ",time - $time," seconds\n"; exit } } else { print {$run->{LOG}} "iurt: a previous iurt for $run->{my_arch} seems dead, cleaning.\n"; unlink $pidfile } } print {$run->{LOG}} "iurt: setting $pidfile pid lock\n"; open my $PID, ">$pidfile" or die "FATAL iurt: 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... my $rpms_dir = "$config->{repository}/$run->{distro}/$run->{my_arch}/media/$run->{media}/"; print {$run->{LOG}} "iurt: checking current packages in $rpms_dir\n"; opendir my $rpmdir, $rpms_dir or die "Could not open $rpms_dir: $!"; foreach my $rpm (readdir $rpmdir) { my ($rarch, $srpm) = update_srpm($rpms_dir, $rpm, $wrong_rpm); $rarch or next; $cache->{queue}{$srpm} = 1; $run{status}{$srpm} = 'ok'; check_version($srpm) } closedir $rpmdir; foreach my $m (@{$config->{all_media}}) { my $synthesis_file = "$config->{repository}/$run->{distro}/$run->{my_arch}/media/$m/media_info/synthesis.hdlist.cz"; if (-f $synthesis_file) { print {$run->{LOG}} "Parsing $synthesis_file\n"; if (open my $syn, "zcat $synthesis_file |") { my @prov; my $nb; while (<$syn>) { if (/^\@provides@(.*)/) { foreach my $p (split '@', $1) { $p =~ /([^[]+)(?:\[(.*)\])?/g; push @prov, $1; $provides->{$1} = $2 || 1 } } elsif (/\@info\@([^@]+)@/) { $nb++; my ($name) = $1 =~ /(.*)-[^-]+-[^-]+\..*$/; foreach (@prov) { $pack_provide->{$_} = $name } @prov = () } } $nb < $config->{minimum_package_number} and die "FATAL iurt: synthesis files seems corrupted, only $nb packages found." } else { die "FATAL iurt: Could not open $synthesis_file\n"; } } } search_packages(0, $cache, $run, $maint, "$config->{repository}/$run->{distro}/SRPMS/$run->{media}/") } sub search_packages { my ($clean, $cache, $run, $maint, @dir) = @_; my ($to_compile, %rep, %done_rpm); foreach my $dir (@dir) { print {$run->{LOG}} "iurt: checking SRPMS dir $dir\n"; 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)) { if (defined $cache->{failure}{$srpm}) { $run->{status}{$srpm} = 'build_failure'; next } my $check_needed = check_needed($srpm); $run->{status}{$srpm} = 'missing_buildrequires' if !$check_needed; if (!$cache->{queue}{$srpm} && $check_needed) { my $hdr = rpm2header("$dir/$srpm"); if (!check_arch($hdr)) { $run->{status}{$srpm} = 'not_on_this_arch'; next } my $changelog = $hdr->queryformat("%{CHANGELOGNAME}"); my ($mail) = $changelog =~ /<(.*@.*)>/; $maint{$srpm} = $mail; print "iurt: will try to compile $srpm\n"; $to_compile++; push @{$run->{todo}}, [ $dir , $srpm ] } foreach my $arch (@{$config->{supported_arch}}) { $ok &&= $cache->{queue}{$srpm} } } if ($clean && ($rep{$srpm} || $ok)) { print "iurt: cleaning $dir/$srpm\n"; unlink "$dir/build/$srpm"; unlink "$dir/$srpm" } $rep{$srpm} = 1 } closedir $rpmdir } $to_compile } sub get_date { my ($sec,$min,$hour,$mday,$mon,$year) = gmtime(time()); $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 add_local_user { my ($chroot_tmp, $run, $luser, $uid) = @_; # 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|)) { if (system(qq|sudo chroot $chroot_tmp useradd -M -u $uid $luser|) && system(qq|sudo chroot $chroot_tmp id $luser|)) { print {$run->{LOG}} "ERROR: setting userid to builder user in chroot failed, trying to check the chroot\n"; check_chroot($chroot, $chroot_tar, $run); } if (system(qq|sudo chroot $chroot_tmp cp -R /home/builder /home/$luser|)) { die "FATAL iurt: could not initialized $luser directory\n"; } if (system(qq|sudo chroot $chroot_tmp chown -R $uid /home/$luser|)) { die "FATAL iurt: could not initialized $luser directory\n"; } } sub create_temp_chroot { my ($run, $cache, $unionfs_tmp, $unionfs_dir, $union_id, $srpm) = @_; if ($unionfs_tmp) { my $mount_point = "$unionfs_dir/unionfs.$run->{run}.$union_id"; print {$run->{LOG}} "Cleaning $mount_point\n" if $run->{verbose} > 1; if (!clean_mnt($mount_point, $run->{verbose})) { dump_cache($run); die "FATAL iurt: could not 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}, $union_id); $tmpfs = "$unionfs_dir/tmpfs.$run->{run}.$union_id"; $chroot_tmp = "$unionfs_dir/unionfs.$run->{run}.$union_id"; mkdir $tmpfs or die "Could not create $tmpfs ($!)"; mkdir $chroot_tmp or die "Could not create $chroot_tmp ($!)"; if ($cache->{no_unionfs}{$srpm}) { $unionfs_tmp = 0; clean_chroot($chroot_tmp, $run) } else { # if the previous package has been built without unionfs, chroot need to be cleaned clean_chroot($chroot_tmp, $run) if !$unionfs_tmp; $unionfs_tmp = 1; system(qq{sudo mount -t tmpfs none $tmpfs &>/dev/null}) and die "FATAL iurt: could not mount $tmpfs ($!)"; system(qq|sudo mount -o dirs=$tmpfs=rw:$home/chroot_$run->{distro_tag}$debug_tag=ro -t unionfs none $chroot_tmp &>/dev/null|) and die "FATAL iurt: could not mount $tmpfs and $home/chroot_$run->{distro_tag}$debug_tag with unionfs ($!)"; system("sudo mount -t proc none $chroot_tmp/proc &>/dev/null") and die "FATAL iurt: could not mount /proc in the chroot $chroot_tmp."; } } else { print {$run->{LOG}} "iurt: installing a new chroot in $chroot_tmp\n" if $run->{verbose} > 1; clean_chroot($chroot_tmp, $run) } $unionfs_tmp, $chroot_tmp } sub add_sudo { my ($run, $config, $chroot, $user) = @_; my $f; perform_command("sudo urpmi $urpmi_options --root $chroot_tmp sudo urpmi", $run, $config, mail => $config->{admin}, timeout => 600, freq => 1, retry => 10, debug_mail => $run->{debug}, error_regexp => 'cannot be installed', wait_regexp => 'database locked', wait_callback => sub { print {$run->{LOG}} "WARNING iurt: urpmi database locked, waiting...\n"; sleep 30; $wait_limit++; if ($wait_limit > 10) { $wait_limit = 0; system(qq{sudo pkill -9 urpmi &>/dev/null}) } },); if (system("sudo urpmi $urpmi_options --root $chroot_tmp sudo urpmi")) { die "FATAL iurt: could not install sudo in the $chroot_tmp ($!)" } my $file = "$chroot/etc/sudoers"; my $f; if (!open $f, qq{| sudo sh -c "cat > $file"}) { print {$run->{LOG}} "ERROR iurt: could not open $file ($!)\n"; return 0 } print $f qq{Cmnd_Alias RPM=/bin/rpm,/usr/sbin/urpmi,/usr/sbin/urpme,/usr/sbin/urpmi.addmedia root ALL=(ALL) ALL $user ALL=(ALL) NOPASSWD:RPM }; close $f; print {$run->{LOG}} "iurt: adding sudo for /bin/rpm, /usr/sbin/urpmi and /usr/sbin/urpme\n"; -f $file or return 0; 1 } sub remove_chroot { my ($run, $dir, $func, $prefix) = @_; if ($run->{clean_all}) { opendir my $chroot_dir, $dir; foreach (readdir $chroot_dir) { next if !-d || /\.{1,2}/; $func->("$dir/$_", $prefix) } } else { foreach my $user (@{$run->{clean}}) { print {$run->{LOG}} "iurt: cleaning old chroot for $user\n"; $func->("$dir/$user", $prefix) } } }