aboutsummaryrefslogtreecommitdiffstats
path: root/lib/Iurt/Chroot.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Iurt/Chroot.pm')
-rw-r--r--lib/Iurt/Chroot.pm515
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;