diff options
Diffstat (limited to 'lib/Iurt/Chroot.pm')
-rw-r--r-- | lib/Iurt/Chroot.pm | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/lib/Iurt/Chroot.pm b/lib/Iurt/Chroot.pm new file mode 100644 index 0000000..5f055fa --- /dev/null +++ b/lib/Iurt/Chroot.pm @@ -0,0 +1,515 @@ +package Iurt::Chroot; + +use strict; +use base qw(Exporter); +use MDV::Distribconf::Build; +use MDK::Common; +use Iurt::Process qw(clean sudo); +use Iurt::Config qw(dump_cache_par); +use Iurt::Util qw(plog); +use File::Temp 'mktemp'; +use File::Path 'mkpath'; + +our @EXPORT = qw( + clean_chroot_tmp + clean_unionfs + clean_all_chroot_tmp + clean_all_unionfs + clean_chroot + dump_rpmmacros + add_local_user + create_temp_chroot + remove_chroot + create_chroot + create_buid_chroot + check_chroot + check_build_chroot +); + +my $sudo = '/usr/bin/sudo'; + +=head2 clean_chroot($chroot, $run, $only_clean) + +Create or clean a chroot +I<$chroot> chroot path +I<$run> is the running environment +I<%only_clean> only clean the chroot, do not create a new one +Return true. + +=cut + +sub clean_chroot { + my ($chroot, $chroot_tar, $run, $config, $o_only_clean, $o_only_tar) = @_; + + plog('DEBUG', "clean chroot"); + if (-d $chroot && !$o_only_tar) { + system("$sudo umount $chroot/proc &> /dev/null"); + system("$sudo umount $chroot/dev/pts &> /dev/null"); + sudo($run, $config, '--rm', '-r', $chroot); + } + + return 1 if $o_only_clean; + + mkdir $chroot; + + # various integrity checking + return 1 if $o_only_tar && -f "$chroot/home/builder/.rpmmacros" && -d "$chroot/home/builder" && -d "$chroot/proc"; + + chdir $chroot; + + system($sudo, 'tar', 'xf', $chroot_tar) and create_build_chroot($chroot, $chroot_tar, $run, $config); + + create_build_chroot($chroot, $chroot_tar, $run, $config) if !-d "$chroot/proc" || !-d "$chroot/home/builder"; + + dump_rpmmacros($run, $config, "$chroot/home/builder/.rpmmacros") or return; + system("$sudo mount none -t proc $chroot/proc &>/dev/null") and return; + system("$sudo mount none -t devpts $chroot/dev/pts &>/dev/null") and return; + 1; +} + +sub dump_rpmmacros { + my ($run, $config, $file) = @_; + my $f; + + #plog("adding rpmmacros to $file"); + + if (!open $f, qq(| $sudo sh -c "cat > $file")) { + plog("ERROR: could not open $file ($!)"); + return 0; + } + my $packager = $run->{packager} || $config->{packager}; + + print $f qq(\%_topdir \%(echo \$HOME)/rpm +\%_tmppath \%(echo \$HOME)/rpm/tmp/ +\%distribution $config->{distribution} +\%vendor $config->{vendor} +\%packager $packager); + # need to be root for permission +} + +sub add_local_user { + my ($chroot_tmp, $run, $config, $luser, $uid) = @_; + my $program_name = $run->{program_name}; + + # change the builder user to the local user id + # FIXME it seems that unionfs does not handle well the change of the + # uid of files + # if (system(qq|sudo chroot $chroot_tmp usermod -u $run->{uid} builder|)) { + + # this should not be necessary as the builder user is supposed to have + # the macros already + + foreach my $p ('RPMS', 'BUILD', 'SPECS', 'SRPMS', 'SOURCES') { + -d "$chroot_tmp/home/builder/rpm/$p" and next; + sudo($run, $config, "--mkdir", "$chroot_tmp/home/builder/rpm/$p"); + } + + dump_rpmmacros($run, $config, "$chroot_tmp/home/builder/.rpmmacros") or return; + + if ($uid) { + if (system($sudo, 'chroot', $chroot_tmp, 'useradd', '-M', '-u', $uid, $luser) || + system("$sudo chroot $chroot_tmp id $luser >/dev/null 2>&1")) { + plog('ERR', "ERROR: setting userid $uid to $luser in " . + "$chroot_tmp failed, checking the chroot"); + check_build_chroot($run->{chroot_path}, $run->{chroot_tar}, $run, + $config) or return; + } + } else { + # the program has been launch as root, setting the home to /home/root for compatibility + system($sudo, 'chroot', $chroot_tmp, 'usermod', '-d', "/home/$luser", '-u', $uid, '-o', '-l', $luser, 'root'); + } + + if (system(qq($sudo chroot $chroot_tmp cp -R /home/builder /home/$luser))) { + plog("ERROR: could not initialized $luser directory"); + return; + } + + if (system(qq($sudo chroot $chroot_tmp chown -R $uid /home/$luser))) { + die "ERROR $program_name: could not initialized $luser directory\n"; + } + + 1; +} + +sub create_temp_chroot { + my ($run, $config, $cache, $union_id, $chroot_tmp, $chroot_tar, $srpm) = @_; + + my $home = $config->{local_home}; + my $debug_tag = $run->{debug_tag}; + my $unionfs_dir = $run->{unionfs_dir}; + + if ($run->{unionfs_tmp}) { + my $mount_point = "$unionfs_dir/unionfs.$run->{run}.$union_id"; + plog(2, "cleaning temp chroot $mount_point"); + if (!clean_mnt($run, $mount_point, $run->{verbose})) { + dump_cache_par($run); + die "FATAL: can't kill remaining processes acceding $mount_point"; + } + my $tmpfs; + + # we cannont just rm -rf $tmpfs, this create defunct processes + # afterwards (and lock particularly hard the urpmi database) + # + $union_id = clean_unionfs($unionfs_dir, $run, $run->{run}, $union_id); + $tmpfs = "$unionfs_dir/tmpfs.$run->{run}.$union_id"; + $chroot_tmp = "$unionfs_dir/unionfs.$run->{run}.$union_id"; + + if (!-d $tmpfs) { + if (!mkpath($tmpfs)) { + plog("ERROR: Could not create $tmpfs ($!)"); + return; + } + } + if (! -d $chroot_tmp) { + if (!mkpath($chroot_tmp)) { + plog("ERROR: Could not create $chroot_tmp ($!)"); + return; + } + } + if ($cache->{no_unionfs}{$srpm}) { + $run->{unionfs_tmp} = 0; + clean_chroot($chroot_tmp, $chroot_tar, $run, $config); + } else { + # if the previous package has been built without unionfs, chroot need to be cleaned + if (!$run->{unionfs_tmp}) { + clean_chroot($chroot_tmp, $chroot_tar, $run, $config); + } else { + # only detar the chroot if not already + clean_chroot($chroot_tmp, $chroot_tar, $run, $config, 0, 1); + } + $run->{unionfs_tmp} = 1; + if (system(qq($sudo mount -t tmpfs none $tmpfs &>/dev/null))) { + plog("ERROR: can't mount $tmpfs ($!)"); + return; + } + if (system(qq($sudo mount -o dirs=$tmpfs=rw:$home/chroot_$run->{distro_tag}$debug_tag=ro -t unionfs none $chroot_tmp &>/dev/null))) { + plog("ERROR: can't mount $tmpfs and $home/chroot_$run->{distro_tag}$debug_tag with unionfs ($!)"); + return; + } + if (system("$sudo mount -t proc none $chroot_tmp/proc &>/dev/null")) { + plog("ERROR: can't mount /proc in chroot $chroot_tmp ($!)"); + return; + } + if (!-d "$chroot_tmp/dev/pts") { + if (sudo($run, $config, "--mkdir", "$chroot_tmp/dev/pts")) { + plog("ERROR: can't create /dev/pts in chroot $chroot_tmp ($!)"); + return; + } + + if (system($sudo, "mount", "-t", "devpts", "none", "$chroot_tmp/dev/pts &>/dev/null")) { + plog("ERROR: can't mount /dev/pts in the chroot $chroot_tmp ($!)"); + return; + } + } + } + } else { + plog("Install new chroot"); + plog('DEBUG', "... in $chroot_tmp"); + clean_chroot($chroot_tmp, $chroot_tar, $run, $config); + } + $union_id, $chroot_tmp; +} + +sub remove_chroot { + my ($run, $dir, $func, $prefix) = @_; + + plog("Remove existing chroot"); + plog('DEBUG', "... dir $dir all $run->{clean_all} prefix $prefix"); + + if ($run->{clean_all}) { + opendir my $chroot_dir, $dir; + foreach (readdir $chroot_dir) { + next if !-d "$dir/$_" || /\.{1,2}/; + plog("cleaning old chroot for $_ in $dir"); + $func->($run, "$dir/$_", $prefix); + } + } else { + foreach my $user (@{$run->{clean}}) { + plog("cleaning old chroot for $user in $dir"); + $func->($run, "$dir/$user", $prefix); + } + } +} + +sub clean_mnt { + my ($run, $mount_point, $verbose) = @_; + return clean($run, $mount_point, "/sbin/fuser", "$sudo /sbin/fuser -k", $verbose); +} + +sub clean_all_chroot_tmp { + my ($run, $chroot_dir, $prefix) = @_; + + plog(1, "cleaning all old chroot remaining dir in $chroot_dir"); + + my $dir; + if (!opendir $dir, $chroot_dir) { + plog("ERROR: can't open $chroot_dir ($!)"); + return; + } + foreach (readdir($dir)) { + /$prefix/ or next; + clean_chroot_tmp($run, $chroot_dir, $_); + } + closedir $dir; +} + +sub clean_unionfs { + my ($unionfs_dir, $_run, $r, $union_id) = @_; + + -d "$unionfs_dir/unionfs.$r.$union_id" or return $union_id; + plog(2, "cleaning unionfs $unionfs_dir/unionfs.$r.$union_id"); + my $nok = 1; + my $path = "$unionfs_dir/unionfs.$r.$union_id"; + + while ($nok) { + $nok = 0; + foreach my $fs ([ 'proc', 'proc' ], [ 'dev/pts', 'devpts' ]) { + my ($dir, $type) = @$fs; + if (-d "$path/$dir" && check_mounted("$path/$dir", $type)) { + plog(1, "clean_unionfs: umounting $path/$dir\n"); + if (system("$sudo umount $path/$dir &>/dev/null")) { + plog("ERROR: could not umount $path/$dir"); + } + } + } + foreach my $t ('unionfs', 'tmpfs') { + # unfortunately quite oftem the unionfs is busy and could not + # be unmounted + + my $d = "$unionfs_dir/$t.$r.$union_id"; + if (-d $d && check_mounted($d, $t)) { + $nok = 1; + system("$sudo /sbin/fuser -k $d &> /dev/null"); + plog(3, "umounting $d"); + if (system(qq($sudo umount $d &> /dev/null))) { + plog(2, "WARNING: could not umount $d ($!)"); + return $union_id + 1; + } + } + } + } + + foreach my $t ('unionfs', 'tmpfs') { + my $d = "$unionfs_dir/$t.$r.$union_id"; + plog(2, "removing $d"); + if (system($sudo, 'rm', '-rf', $d)) { + plog("ERROR: removing $d failed ($!)"); + return $union_id + 1; + } + } + $union_id; +} + +sub clean_chroot_tmp { + my ($run, $chroot_dir, $dir) = @_; + my $d = "$chroot_dir/$dir"; + + foreach my $m ('proc', 'dev/pts') { + if (system("$sudo umount $d/$m &>/dev/null") && $run->{verbose} > 1) { + plog("ERROR: could not umount /$m in $d/"); + } + } + + plog(1, "cleaning $d"); + system("$sudo /sbin/fuser -k $d &> /dev/null"); + plog(1, "removing $d"); + system($sudo, 'rm', '-rf', $d); +} + +sub check_mounted { + my ($mount_point, $type) = @_; + + my $mount; + if (!open $mount, '/proc/mounts') { + plog("ERROR: could not open /proc/mounts"); + return; + } + $mount_point =~ s,//+,/,g; + local $_; + while (<$mount>) { + return 1 if /^\w+ $mount_point $type /; + } + 0; +} + +sub create_build_chroot { + my ($chroot, $chroot_tar, $run, $config) = @_; + create_chroot($chroot, $chroot_tar, $run, $config, + { packages => $config->{basesystem_packages} }); +} + +sub create_chroot { + my ($chroot, $chroot_tar, $run, $config, $opt) = @_; + my $tmp_tar = mktemp("$chroot_tar.tmp.XXXXXX"); + my $tmp_chroot = mktemp("$chroot.tmp.XXXXXX"); + my $rebuild; + my $clean = sub { + plog("Remove temporary chroot tarball"); + sudo($run, $config, '--rm', '-r', $tmp_chroot, $tmp_tar); + }; + + plog('NOTIFY', "creating chroot"); + plog('DEBUG', "... with packages " . join(', ', @{$opt->{packages}})); + + if (mkdir($tmp_chroot) && (!-f $chroot_tar || link $chroot_tar, $tmp_tar)) { + if (!-f $chroot_tar) { + plog("rebuild chroot tarball"); + $rebuild = 1; + if (!build_chroot($run, $config, $tmp_chroot, $chroot_tar, $opt)) { + $clean->(); + return; + } + } else { + plog('DEBUG', "decompressing /var/log/qa from $chroot_tar in $tmp_chroot"); + system($sudo, 'tar', 'xf', $chroot_tar, '-C', $tmp_chroot, "./var/log/qa"); + + my $qa; + if (open $qa, "$tmp_chroot/var/log/qa") { + my $ok; + my $f; + while (!$ok && ($f = <$qa>)) { + chomp $f; + if (!-f "$config->{basesystem_media_root}/media/$config->{basesystem_media}/$f") { + plog('DEBUG', "$f has changed"); + plog('NOTIFY', "Rebuilding chroot tarball"); + + $rebuild = 1; + sudo($run, $config, '--rm', '-r', $tmp_chroot); + mkdir $tmp_chroot; + if (!build_chroot($run, $config, $tmp_chroot, $chroot_tar, $opt)) { + $clean->(); + return; + } + $ok = 1; + } + } + } else { + plog('DEBUG', "can't open $tmp_chroot/var/log/qa"); + plog('ERR', "can't check chroot, recreating"); + + if (!build_chroot($run, $config, $tmp_chroot, $chroot_tar, $opt)) { + $clean->(); + return; + } + } + } + link $tmp_tar, $chroot_tar; + } else { + die "FATAL: could not initialize chroot ($!)\n"; + } + + if (!-d $chroot || $rebuild) { + plog('DEBUG', "recreate chroot $chroot"); + plog('NOTIFY', "recreate chroot"); + my $urpmi = $run->{urpmi}; + $urpmi->clean_urpmi_process($chroot); + sudo($run, $config, '--rm', '-r', $chroot, $tmp_tar); + mkdir_p $chroot; + system($sudo, 'tar', 'xf', $chroot_tar, '-C', $chroot); + plog('NOTIFY', "chroot recreated in $chroot_tar (live in $chroot)"); + } + + $clean->(); + + 1; +} + +sub build_chroot { + my ($run, $config, $tmp_chroot, $chroot_tar, $opt) = @_; + + plog('DEBUG', "building the chroot with " + . join(', ', @{$opt->{packages}})); + + sudo($run, $config, "--mkdir", "-p", "$tmp_chroot/dev/pts", + "$tmp_chroot/etc/sysconfig", "$tmp_chroot/proc"); + + # create empty files + foreach ('/etc/ld.so.conf', '/etc/mtab') { + system($sudo, 'touch', "$tmp_chroot$_"); + } + + system($sudo, 'mknod', "$tmp_chroot/dev/null", 'c', 1, 3); + system("$sudo chmod a+rw $tmp_chroot/dev/null"); + + #system(qq($sudo sh -c "echo 127.0.0.1 localhost > $tmp_chroot/etc/hosts")); + # warly some program perform a gethostbyname(hostname) and in the cluster the + # name are not resolved via DNS but via /etc/hosts + sudo($run, $config, '--cp', "/etc/hosts", "$tmp_chroot/etc/"); + sudo($run, $config, '--cp', "/etc/resolv.conf", "$tmp_chroot/etc/"); + + sudo($run, $config, "--initdb", $tmp_chroot); + # install chroot + my $urpmi = $run->{urpmi}; + $urpmi->set_command($tmp_chroot); + + # 20060826 warly urpmi --root does not work properly + $urpmi->install_packages( + "chroot", + $tmp_chroot, + $run->{local_spool}, + {}, + 'initialize', + "[ADMIN] creation of initial chroot failed on $run->{my_arch}", + { maintainer => $config->{admin} }, + @{$opt->{packages}} + ); + + if (-f "$tmp_chroot/bin/rpm") { + system($sudo, 'sh', '-c', "chroot $tmp_chroot rpm -qa --qf '\%{NAME}-\%{VERSION}-\%{RELEASE}.\%{ARCH}.rpm\n' > $tmp_chroot/var/log/qa"); + # + # CM: Choose a sub-500 uid to prevent collison with $luser + # + system($sudo, 'chroot', $tmp_chroot, 'adduser', '-o', '--uid', 499, 'builder'); + sudo($run, $config, "--mkdir", "-p", "$tmp_chroot/home/builder/rpm"); + foreach my $p (qw(RPMS BUILD SPECS SRPMS SOURCES tmp)) { + -d "$tmp_chroot/home/builder/rpm/$p" and next; + sudo($run, $config, "--mkdir", "$tmp_chroot/home/builder/rpm/$p"); + } + system($sudo, 'chown', '-R', 499, "$tmp_chroot/home/builder"); + sudo($run, $config, "--rm", "$tmp_chroot/var/lib/rpm/__db*"); + return !system($sudo, 'tar', 'czf', $chroot_tar, '-C', $tmp_chroot, '.'); + } +} + +sub check_build_chroot { + my ($chroot, $chroot_tar, $run, $config) = @_; + + check_chroot($chroot, $chroot_tar, $run, $config, + { packages => $config->{basesystem_packages} }); +} + +sub check_chroot { + my ($chroot, $chroot_tar, $run, $config, $opt) = @_; + + plog('DEBUG', "checking basesystem tar"); + + my (@stat) = stat $chroot_tar; + + if (time -$stat[9] > 604800) { + plog('WARN', "chroot tarball too old, force rebuild"); + sudo($run, $config, '--rm', '-r', $chroot, $chroot_tar); + } + create_chroot($chroot, $chroot_tar, $run, $config, $opt); +} + +sub clean_all_unionfs { + my ($run, $unionfs_dir) = @_; + + plog(2, "Cleaning old unionfs remaining dir in $unionfs_dir"); + + my $dir; + if (!opendir $dir, $unionfs_dir) { + plog(0, "FATAL could not open $unionfs_dir ($!)"); + return; + } + + foreach (readdir $dir) { + /unionfs\.((?:0\.)?\d+)\.(\d*)$/ or next; + clean_unionfs($unionfs_dir, $run, $1, $2); + } + + closedir $dir; +} + + +1; |