#!/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 --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 get_maint check_arch %arch_comp get_package_prefix); use Data::Dumper; use URPM; use Iurt::Urpmi; use Iurt::Chroot qw(add_local_user create_temp_chroot remove_chroot create_build_chroot clean_chroot); use Iurt::Process qw(perform_command 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 Fcntl qw(:flock SEEK_END); use Mkcd::Commandline qw(parseCommandLine usage); use MDK::Common; use Filesys::Df qw(df); use POSIX; # copied from drakx' standalone: sub bug_handler { my ($error, $is_signal) = @_; # exceptions in eval are OK: return if $error && $^S && !$is_signal; # we want the full backtrace: $error .= "\n" if $is_signal; $error .= backtrace() if $error; warn "We got an uncatched exception:\n$error\n"; #exit(1); } $SIG{SEGV} = sub { bug_handler(@_, 1) }; #$SIG{__DIE__} = \&bug_handler; $SIG{TERM} = sub { warn "Got KILLED by SIGTERM at " . strftime("%c", localtime()) . " .\n"; exit(1); }; my $program_name = 'iurt'; # 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, "[--chrooted-urpmi ] [--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] [--upload [--markrelease] [--source]] [--dir] [--help foo?] [--log filename] [--status] [--repository ] [--rpmmacros [...]] {--config-help} --chroot --arch {i586|x86_64|ppc} --distro {cauldron|...} | --build-user --rebuild {cauldron|...} {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 cauldron x86_64 /SRPMS/main/release/mkcd-4.2.5-1mdv2007.1.src.rpm", 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" ], [ "", "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, " ... ", "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] ", "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, " ... ", "Clean remaining chroot before runing", sub { $run{clean} = \@_ }, "Activating clean chroot flag" ], [ "", "parallel", 1, "", "Build up to packages in parallel", sub { ($run{parallel}) = @_; 1 }, "Enabling parallel build" ], [ "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" ], [ "", "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" ], [ "l", "log", 1, "", "Log file.", sub { $run{log} = pop @_; open my $log, ">$run{log}" or die "unable to open $run{log}\n"; $run{logfd} = $log; print *$log, "command line: @ARGV\n"; 1; }, "Log file" ], [ "m", "media", -1, " ... ", "Media to rebuild", sub { ($run{media}) = @_; 1 }, "Adding a media to rebuild" ], [ "", "build-all", 0, "", "Build all packages of the media, even if they are up to date", sub { $run{build_all} = 1 }, "Setting the full build flag" ], [ "", "num_shards", 1, "", "Number of shards for building", sub { ($run{num_shards}) = @_; 1 }, 'Setting number of shards' ], [ "", "shard_id", 1, "", "Shard id (starts at 0)", sub { ($run{shard_id}) = @_; 1 }, 'Setting shard id' ], [ "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, "", "Use a specific packager", sub { ($run{packager}) = @_ }, 'Setting packager tag' ], [ "", "build-user", 1, "", "Use this username to build package", sub { ($run{user}) = @_ }, 'Setting build username' ], [ "r", "rebuild", -2, " ... ", "Rebuild the packages, e.g. $program_name -r cauldron x86_64 /home/foo/rpmbuild/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$/ or 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" ], [ "", "rpmmacros", -1, " .. ", "Additional rpm macros to define", sub { $run{rpmmacros} = \@_ }, 'Setting rpm macros' ], [ "", "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, "", "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" ], [ "", "delete-on-success", 0, "", "Don't keep generated packages and their logs", sub { $run{delete_on_success} = 1 }, "Setting the delete on success flag" ], [ "", "discard-packages", 0, "", "Don't save built packages, only keep the logs", sub { $run{discard_packages} = 1 }, "Setting the discard packages flag" ], [ "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 rpmbuild -b (p c i l b a s) instead of rpmbuild -ba and then open a shell in the chroot", sub { ($run{stop}) = @_; 1; }, "Setting rpm build option" ], [ "", "repository", 1, "", "Set a repository path if one is not created in the configuration file", sub { ($run{repository}) = @_; 1; } , "Setting the repository" ], [ "", "status", 1, "", "Send a status mail to the provided mail address", sub { ($run{status_mail}) = @_; 1; }, "Setting status mail option" ], [ "", "with", 1, "", "Use specified --with flag with rpm (can be used multiple times)", sub { $run{with_flags} .= " --with " . $_[0]; 1; }, "Adding specified extra --with parameter to rpm" ], [ "", "without", 1, "", "Use specified --without flag with rpm (can be used multiple times)", sub { $run{with_flags} .= " --without " . $_[0]; 1; }, "Adding specified extra --without parameter to rpm" ], # [ short option, long option, # of args, syntax description, # action description, action, execution message ] ############################# [ "", "additional-media", [ [ "", "additional-media", 1, "", "Use additional medias (media prefix is like http:///server.mandriva.com/dis/)", sub { my ($tmp, @arg) = @_; $tmp->[0] ||= {}; push @$tmp, @arg; 1; }, "Setting additional medias options" ], [ "m", "media", -1, " ... ", "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] ", "Also uses these medias (media prefix is like http:///server.mandriva.com/dis/)", sub { my ($opt, $media) = @_; $opt->{repository} = $media; $run{additional_media} = $opt; 1; }, "Activating additional medias" ], ############################### [ "", "icecream", 1, "", "Enables icecream usage by procs", sub { $run{icecream} = $_[0]; }, "Enabling icecream usage" ], ############################### [ "", "storage", 1, "[btrfs|tar]", "Select how to store reference chroot", sub { $run{storage} = $_[0]; }, "Setting storage" ], ); open(my $LOG, ">&STDERR"); plog_init($program_name, $run{logfd} || $LOG, 7, 1); # For parsing command line # Display version information # my $version = '0.6.28'; plog("MSG", "This is iurt version $version"); 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('ERROR', $t->[2]); } # Use the real verbose level plog_init($program_name, $run{logfd} || $LOG, $run{verbose}, 1); $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"; my $sysconfigfile = "/etc/iurt/build/$run{distro_tag}.conf"; my $config = {}; foreach my $f ($configfile, $sysconfigfile) { plog('DEBUG', "load config: $f"); if (-f $f) { $config = eval(cat_($f)) or die "FATAL $program_name: syntax error in $f"; last; } } if ($run{repository}) { plog('DEBUG', "overriding configuration repository by the one given in the command line"); $config->{repository} = $run{repository}; } my %config_usage = ( admin => { desc => 'Mail of the administrator of packages builds', default => '' }, all_media => { desc => 'List of known media', default => { 'main' => [ 'release' ], 'contrib' => [ 'release' ] } }, base_media => { desc => 'List of base media used to build chroot', default => [ 'core/release' ], }, basesystem_packages => { desc => 'List of packages needed for the chroot creation', default => [ 'basesystem-minimal', 'rpm-build', 'sudo', 'urpmi', 'curl', ] }, build_timeout => { desc => 'Maximum build time after which the build process is terminated (in seconds)', default => { default => 18000, }, }, build_stalled_timeout => { desc => 'Maximum build time after which the build process is terminated if it seems stalled', default => { default => 300, }, }, pidfile_home => { desc => 'Where to store the pidfile files', default => "$HOME/.bugs" }, check_binary_file => { desc => 'Packages rebuild should be checked, however sometime rpm is segfaulting and the test is not correct', default => 0 }, chroot_base => { desc => 'Where to store chroots', default => $HOME }, chroot_tar_suffix => { desc => 'Suffix for the chroot tarball, must be supported by tar', default => '.gz' }, iurt_root_command => { desc => 'Program to run sudo command', default => '/usr/sbin/iurt_root_command' }, distribution => { desc => 'Name of the packages distribution', default => 'Mageia' }, email_domain => { desc => 'Domain to append to usernames when sending emails', default => 'mageia.org' }, env => { desc => 'Environment variables to export', default => { PERL_EXTUTILS_AUTOINSTALL => "--skipdeps", PERL_AUTOINSTALL => "--skipdeps" } }, home => { desc => 'Home dir', default => $HOME }, 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 => '' }, 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 => 'Mageia.Org' }, additional_media => { desc => 'Additional medias to be used', default => [] }, icecream => { desc => 'Enabled icecream usage and uses N procs', default => 0 }, ); if ($run{config_usage}) { config_usage(\%config_usage, $config); exit(); } 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 --tune-rpm=all --nolock --auto --no-recommends --ignoresize $config->{urpmi_options}"); $run{urpmi} = $urpmi; if (!$run{chrooted_urpmi} && $run{group}) { die "FATAL $program_name: option --chrooted-urpmi is mandatory if --group is selected"; } $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 ($run{my_arch} ne $real_arch && !$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); if ($config->{env}) { foreach my $var (keys %{$config->{env}}) { plog('DEBUG', "Setting $var to $config->{env}{$var}"); $ENV{$var} = $config->{env}{$var}; } } $config->{upload} .= "$run{distro}/$run{media}"; if ($run{icecream}) { push @{$config->{basesystem_packages}}, 'icecream'; } my $lock = $run{media}; if (!$lock && $run{chroot}) { $lock = 'chroot'; } $run{lock} = $lock; if (!$run{debug} && $run{media} || $run{chroot}) { $run{pidfile_home} = $config->{pidfile_home}; mkpath $run{pidfile_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 =~ s!/+!/!g; #/ 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 = { rpm_srpm => {}, queue => {}, warning => {}, run => 1, needed => {}, }; $run{cache} = $cache; empty_status($local_spool, \%run); 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}; $run{to_compile} = $to_compile; plog("Packages to build: $to_compile"); my ($fulldate, $daydate) = get_date(); $run{run} = "0.$fulldate"; $run{daydate} = $daydate; plog('DEBUG', "using $run{run} as chroot extension"); $run{user} ||= $ENV{USER}; die "Iurt should not be executed as root.\n" if $run{user} eq "root"; $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 on $config->{iurt_root_command} to run $program_name\n"; my $debug_tag = $run{debug} && '_debug'; $run{debug_tag} = $debug_tag; my (%done, $done); $run{done} = \%done; my $home = $config->{local_home}; my $chroot_base = $config->{chroot_base}; my ($chroot_name, $chroot_tmp, $chroot, $chroot_ref); $chroot_name = "chroot_$run{distro_tag}$debug_tag.$run{my_arch}"; if (!$run{use_old_chroot}) { $chroot_tmp = "$chroot_base/chroot_tmp"; if (!-d $chroot_tmp) { mkdir $chroot_tmp; } else { remove_chroot(\%run, $config, $chroot_tmp, $chroot_name); } mkdir_p("$chroot_tmp/$run{user}"); $chroot_tmp = "$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; if ($run{storage} eq 'btrfs') { $chroot_ref = "$chroot_base/$chroot_name"; } else { $chroot_ref = "$chroot_base/$chroot_name.tar$run{chroot_tar_suffix}"; } $run{chroot_ref} = $chroot_ref; # 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") { create_build_chroot($chroot, $chroot_ref, \%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}) { create_temp_chroot(\%run, $config, $chroot_tmp, $chroot_ref) or die "FATAL $program_name: could not create temporary chroot"; } add_local_user($chroot_tmp, $config, $luser, $run{uid}) or die "FATAL $program_name: could not add local user"; #$urpmi->set_command($chroot_tmp); $urpmi->urpmi_command($chroot_tmp); $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($chroot_tmp, $luser); plog('NOTIFY', "dumping to a chrooted shell into $chroot_tmp"); exec $sudo, $config->{iurt_root_command}, '--chroot', $chroot_tmp, '/bin/su', '-', $luser, '-c', "$config->{prompt} bash"; die "FATAL $program_name: could not exec chroot to $chroot_tmp ($!)"; } # If not using --shell or --stop, we don't want an interactive build if (!$run{stop}) { close STDIN; } # 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 -x', "$local_spool/log/", "$config->{rsync_to}/$run{distro_tag}/$run{my_arch}/$run{media}/log/"); } # 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}) { $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; sub rebuild_one { my ($dir, $srpm, $_status) = @_; # 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"; plog('NOTIFY', "Build package $srpm [$done/$to_compile]"); # When rebuilding all the media, src.rpm can be removed from mirror before we work on them unless (-f "$dir/$srpm") { plog('WARNING', "$dir/$srpm missing"); $run{status}{$srpm} = 'missing'; return $srpm; } # FIXME unfortunately urpmi stalls quite often my $retry = 0; retry: $urpmi->clean_urpmi_process; if (!$run{use_old_chroot}) { plog('DEBUG', 'Not reusing old chroot'); $chroot_tmp = create_temp_chroot(\%run, $config, $chroot_tmp, $chroot_ref) or return $srpm; } if (!$urpmi->urpmi_command($chroot_tmp)) { plog('ERROR', "Creating chroot failed.\nCommand was: $chroot_tmp"); return $srpm; } $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm$/ or return $srpm; 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 $chroot_tmp"); add_local_user($chroot_tmp, $config, $luser, $run{uid}) or return $srpm; my $old_srpm = $srpm; my ($ret, $spec); ($ret, $srpm, $spec) = $urpmi->recreate_srpm(\%run, $config, $chroot_tmp, $dir, $srpm, $luser, $retry); if ($ret == -1) { if (create_build_chroot($run{chroot_path}, $run{chroot_ref}, \%run, $config)) { $retry = 1; goto retry; } else { $ret = 0; } } if (!$ret) { # CM: experimental: fail if we can't regenerate the srpm # This should eliminate bouncers that block the input queue # $srpm = $old_srpm; $run{status}{$srpm} = 'recreate_srpm_failure'; return $srpm; } (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 for $srpm"); my $path_srpm = "$chroot_tmp/home/$luser/rpmbuild/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'; return $srpm; } # 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("rpm --root $chroot_tmp -qa | sort", \%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 = "rpmbuild --rebuild $run{with_flags} /home/$luser/rpmbuild/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($chroot_tmp, $luser); $command = "rpmbuild -b$run{stop} /home/$luser/rpmbuild/SPECS/$spec"; } my ($srpm_name) = $srpm =~ /(?:.*:)?(.*)-[^-]+-[^-]+\.src\.rpm$/; my $icecream; if ($run{icecream}) { $icecream = "RPM_BUILD_NCPUS=$run{icecream}"; } if (!perform_command(qq(chroot $chroot_tmp /bin/su - $luser -c "TMP=/home/$luser/tmp/ $icecream $command"), \%run, $config, $cache, use_iurt_root_command => 1, 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}{default}, stalled_timeout => $config->{build_stalled_timeout}{$srpm_name} || $config->{build_stalled_timeout}{default}, srpm => $srpm, debug_mail => $run{debug}, cc => $cc, log => $log_dir, 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, $config->{iurt_root_command}, '--chroot', $chroot_tmp, '/bin/su', '-', $luser, '-c', "$config->{prompt} bash"); exit(); } plog('DEBUG', "calling callback for $opt->{hash}"); if ($output =~ m!/bin/ld: cannot find -l(\S*)|configure.*[^W]error.* (?:-l(\S+)|(\S+) includes)!) { my $missing = $1; my @rpm = find_provides(\%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} = "Maintainer not found <$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 $srpm; } 1; }, freq => 1)) { $run{status}{$srpm} = 'build_failure'; return $srpm; } # 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/rpmbuild/RPMS/"; my @packages; foreach my $bindir (readdir $binfh) { -d "$chroot_tmp/home/$luser/rpmbuild/RPMS/$bindir" or return $srpm; opendir my $rpmfh, "$chroot_tmp/home/$luser/rpmbuild/RPMS/$bindir"; push @packages, map { "$chroot_tmp/home/$luser/rpmbuild/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 return $srpm; } 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'; 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 { # drop packages and logs if we only want failure logs if ($run{delete_on_success}) { system("rm -rf $local_spool/log/$srpm/"); } elsif (!$run{discard_packages}) { plog('OK', "build successful, copying packages to $local_spool."); if (system("cp $chroot_tmp/home/$luser/rpmbuild/RPMS/*/*.rpm $local_spool &>/dev/null")) { # If copy fails (like disk full), report a failure and delete partially copied files plog('ERROR', "ERROR: could not copy rpm files from $chroot_tmp/home/$luser/rpmbuild/RPMS/ to $local_spool ($!)"); foreach my $package (@packages) { unlink "$local_spool/$package"; } } } if ($run{copy_srpm}) { # replace the old srpm unlink "$local_spool/$old_srpm"; if (system("cp $chroot_tmp/home/$luser/rpmbuild/SRPMS/$srpm $local_spool &>/dev/null")) { # If copy fails (like disk full), remove partially copied file plog('ERROR', "ERROR: could not copy $srpm from $chroot_tmp/home/$luser/rpmbuild/SRPMS/ to $local_spool ($!)"); unlink "$local_spool/$srpm"; } } process_queue($config, \%run, \@wrong_rpm, 1); } return $srpm; } my $rebuild; $run{group} = 0 if @{$run{todo}} == 1; if ($run{group}) { $rebuild = 1; $urpmi->set_local_media($local_spool); $urpmi->order_packages(\%provides, $luser) or die "FATAL $program_name: could not order packages"; } # # The build loop # my $prev_done = $done; do { $rebuild = 0; $done = $prev_done; my $i; my %children; for ($i; $i < @{$run{todo}}; $i++) { my ($dir, $srpm, $status) = @{$run{todo}[$i]}; if ($run{num_shards} && ($i % $run{num_shards}) != $run{shard_id}) { plog('DEBUG', "Skipping $srpm, not for my shard"); next; } $done{$srpm} and next; $done{$srpm} = 1; check_version(\%run, $srpm, \%srpm_version) or next; if ($run{debug}) { $run{debug}++ == 2 and exit() } $done++; if ($run{parallel}) { my $pid; # First cleanup all the finished ones do { $pid = waitpid(-1, WNOHANG); if ($pid > 0) { plog('INFO', "Child $pid has exited"); delete $children{$pid}; } } while $pid > 0; while (scalar keys %children >= $run{parallel}) { plog('INFO', "Too many children, waiting to fork more"); $pid = waitpid(-1, 0); delete $children{$pid} if $pid > 0; } # TODO: check load, free memory and free disk $pid = fork(); if ($pid) { #parent $children{$pid} = 1; } elsif ($pid == 0) { #child $chroot_tmp .= "_" . int($i); $srpm = rebuild_one($dir, $srpm, $status); write_status($local_spool, \%run, $srpm); clean_chroot($chroot_tmp, \%run, $config); exit(); } else { die "could not fork"; } } else { $srpm = rebuild_one($dir, $srpm, $status); write_status($local_spool, \%run, $srpm); } } if ($run{parallel}) { foreach my $pid (keys %children) { plog('INFO', "Waiting for process $pid to exit"); waitpid($pid, 0); delete $children{$pid}; } } if ($run{group}) { my $i; for ($i; $i < @{$run{todo}}; $i++) { my (undef, $srpm) = @{$run{todo}[$i]}; if (!$run{done}{$srpm}) { $rebuild = $urpmi->order_packages(\%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/rpmbuild/RPMS/*/*.rpm $local_spool &>/dev/null") and plog('ERROR', "ERROR: could not copy rpm files from $chroot_tmp/home/$luser/rpmbuild/RPMS/ to $local_spool ($!)"); if ($run{copy_srpm}) { system("cp $chroot_tmp/home/$luser/rpmbuild/SRPMS/*.src.rpm $local_spool &>/dev/null") and plog('ERROR', "ERROR: could not copy SRPMS from $chroot_tmp/home/$luser/rpmbuild/SRPMS/ to $local_spool ($!)"); } } else { plog('FAIL', "some packages could not be compiled."); } } } } while $rebuild; if (!$run{debug} && !$run{use_old_chroot}) { clean_chroot($chroot_tmp, \%run, $config); } plog("reprocess generated packages queue"); process_queue($config, \%run, \@wrong_rpm); 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"; } } 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 -x', "$local_spool/log/", "$config->{rsync_to}/$run{distro_tag}/$run{my_arch}/$run{media}/log/"); } 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, $o_quiet) = @_; return if !$run->{upload} && $o_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; } } unlink "$dir/$rpm"; $cache->{queue}{$srpm} = 1; } closedir $rpmdir; } sub check_version { my ($run, $srpm, $srpm_version) = @_; my ($srpm_name) = $srpm =~ /(.*)-[^-]+-[^-]+\.src\.rpm/; $run->{build_all} and return 1; if (URPM::ranges_overlap("= $srpm", ">= $srpm_version->{$srpm_name}")) { $srpm_version->{$srpm_name} = $srpm; return 1; } 0; } sub check_pid { my ($run) = @_; 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; } } 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 "); 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 only on parsing the synthesis, hoping that they are correct, however this scan is very fast, so... if (!$run->{build_all}) { 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($run, $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"); # FIXME: this is reinventing the wheel and will fail if default compressor change: if (open my $syn, "zcat $synthesis_file |") { local $_; while (<$syn>) { my @prov; if (/^\@provides@(.*)/) { foreach my $p (split '@', $1) { if ($p =~ /([^[]+)(?:\[(.*)\])?/g) { push @prov, $1; $provides->{$1} = $2 || 1; } } } elsif (/\@info\@([^@]+)@/) { my $p = $1; my ($name) = $p =~ /(.*)-[^-]+-[^-]+\./; $provides->{$p} = 1; $pack_provide->{$_} = $name foreach @prov; } } } 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($run, $srpm, $srpm_version)) { 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'; write_status($local_spool, \%run, $srpm); 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 ]; } $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 ($chroot, $user) = @_; my $tmpfile = "/tmp/sudoers"; my $file = "$chroot/etc/sudoers"; my $f; if (!open $f, ">$tmpfile") { 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; chmod 0440, $tmpfile; plog("adding sudo for /bin/rpm, /usr/sbin/urpmi and /usr/sbin/urpme"); my $ret = sudo($config, '--cp', $tmpfile, $file); unlink $tmpfile; if (!$ret) { plog("ERROR: could not write $file ($!)"); return 0; } return -f $file; } sub status_file { my ($local_spool, $run) = @_; my $media = $run->{media} ? "$run->{media}." : ""; return "$local_spool/log/status.${media}log"; } sub empty_status { my ($local_spool, $run) = @_; my $status_file = status_file($local_spool, $run); truncate($status_file, 0); } sub write_status { my ($local_spool, $run, $srpm) = @_; return unless $run{status}{$srpm}; my $status_file = status_file($local_spool, $run); if (open my $file, ">>$status_file") { flock($file, LOCK_EX); seek($file, 0, SEEK_END); print $file "$srpm: $run->{status}{$srpm}\n"; flock($file, LOCK_UN); } } # # 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, $config); } sub find_provides { my ($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() { return 0 == system("$sudo -l -n $config->{iurt_root_command} &>/dev/null