aboutsummaryrefslogtreecommitdiff
blob: 31278d5b39f672901ee5d32c6ba9f8d0e89492a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# vim: set sw=4 sts=4 et :
# Copyright: 2008 Gentoo Foundation
# Author(s): Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
# License: GPL-3
#
# Immortal lh!
#

import os, shutil, subprocess, re
import os.path as osp
from .. import config, sync
from time import strftime

class PristineChroot(object):
    """
    Pristine Chroot
    """
    def __init__(self, stage_filename):
        """
        @param stage: Filename of Stage tarball which has the chroot
        @type stage: string
        """
        # stage-my.tar.bz2 -> stage-my
        self.dir = osp.join(config.CHROOT_DIR, stage_filename[:stage_filename.rfind('.tar')])
        self.stage = stage_filename
        self.tar_args = (osp.join(config.STAGE_DIR, self.stage), self.dir)

    def _extract_stage(self):
        os.makedirs(self.dir)
        # Replace with a jobuild.sh unpack later.
        subprocess.check_call('tar xf "%s" -C "%s"' % self.tar_args, shell=True)
        os.mkdir(self.dir+'/usr/portage')

    def check(self, fix=False):
        """Verify that the pristine is intact"""
        if config.VERBOSE:
            print "Cross-checking with stage tarball \"%s\"" % self.stage
        process = subprocess.Popen('tar jfd "%s" -C "%s"' % self.tar_args, stderr=subprocess.PIPE, shell=True)
        if config.VERBOSE:
            print "PID: %s" % process.pid
        output = process.communicate()[1]
        if output.count('No such file or directory') > 0:
            print "Chroot is no longer pristine."
            regex = re.compile(r'tar: (.*?):.*')
            output = regex.sub(r'"\1"', output)
            if config.VERBOSE:
                print "The following files are missing:"
                print output
            if not fix:
                return False
            print "Fixing..."
            tar_args = self.tar_args + output.replace('\n', ' ')
            subprocess.check_call('tar xf \"%s\" -C \"%s\" %s' % tar_args, shell=True)
            print "Chroot is once again pristine :)"
        return True

    def setup(self):
        """Extract the chroot if required"""
        if osp.exists(self.dir):
            print "Pristine ready."
            return True
        if config.VERBOSE:
            print "Extracting from stage...",
        self._extract_stage()
        print "Pristine ready."
        return True

    def reset(self):
        """Re-extract the chroot from the tarball"""
        if config.VERBOSE:
            print "Removing existing chroot..."
        shutil.rmtree(self.dir)
        if config.VERBOSE:
            print "Extracting %s" % self.stage
        self._extract_stage()
        print "Pristine ready."


class WorkChroot(object):
    """
    Chroot where all the action happens
    """
    def __init__(self, jobdir, stage_file):
        """
        @param job: Job to be run inside the chroot
        @type job: L{autotua.Job}
        """
        self.pristine = PristineChroot(stage_file)
        self.jobdir = jobdir
        self.chrootdir = osp.join(self.jobdir, 'chroot')

    # Hmmmm. Maybe all this should be in a module of it's own.
    def _clean_mounts(self):
        # /proc/mounts is more reliable than mtab (which is what `mount` uses)
        mounts = open('/proc/mounts', 'r').read()
        regex = re.compile(r'%s/[^ ]+' % self.chrootdir.replace(' ', r'\\040'))
        for mount in regex.findall(mounts):
            mount = mount.replace(r'\040(deleted)', '').replace(r'\040', ' ')
            subprocess.check_call('umount "%s"' % mount, shell=True)

    def _bind(self, src, dest, ro=True):
        """
        Bind mount src onto dest inside self.chrootdir
        Mount read-only by default
        """
        if not dest.startswith('/'):
            dest = '/'+dest
        dest = self.chrootdir+dest
        options = 'bind'
        if ro:
            options += ',ro'
        subprocess.check_call('mount -o %s "%s" "%s"' % (options, src, dest), shell=True)

    def _setup_mounts(self):
        for dir in ['/dev', '/sys', '/proc']:
            self._bind(dir, dir)
        # FIXME: else: Fetch portage tarball and use.
        if config.PORTAGE_DIR:
            if not osp.isdir(config.PORTAGE_DIR):
                print "\"%s\" is not a directory, cannot mount" % config.PORTAGE_DIR
            else:
                self._bind(config.PORTAGE_DIR, '/usr/portage')
        if config.DISTFILES_DIR and not os.path.samefile(config.DISTFILES_DIR, config.PORTAGE_DIR+'/distfiles'):
            if not osp.isdir(config.DISTFILES_DIR):
                print "\"%s\" is not a directory, cannot mount" % config.DISTFILES_DIR
            else:
                self._bind(config.DISTFILES_DIR, '/usr/portage/distfiles', ro=False)
        self._bind(config.AUTOTUA_DIR+'/bin', config.CHAUTOTUA_DIR+'/bin')
        self._bind(self.jobdir+'/jobtage', config.CHAUTOTUA_DIR+'/jobtage')

    def setup(self):
        """
        Clean existing mounts, if any
        rsync from PristineChroot
        Mount stuff.
        """
        if not self.pristine.setup():
            return False
        # Tidy up incase we screwed up last time
        self.tidy()
        # self.pristine.dir/ => rsync *contents* to self.chrootdir
        print "Preparing Work Chroot..."
        sync.Syncer(uri=self.pristine.dir+"/", destdir=self.chrootdir, scheme='rsync-nc').sync()
        for dir in ['bin', 'jobfiles', 'jobtage', 'src']:
            os.makedirs('%s/%s/%s' % (self.chrootdir, config.CHAUTOTUA_DIR, dir))
        self._setup_mounts()
        print "Work Chroot ready."

    def tidy(self):
        """
        Cleanup when done.
        """
        self._clean_mounts()
        if osp.isdir(self.chrootdir+config.CHAUTOTUA_DIR):
            if not osp.isdir(self.chrootdir+config.CHAUTOTUA_DIR+'-old'):
                os.makedirs(self.chrootdir+config.CHAUTOTUA_DIR+'-old')
            shutil.move(self.chrootdir+config.CHAUTOTUA_DIR,
                        self.chrootdir+config.CHAUTOTUA_DIR+'-old/autotua-'+strftime('%Y%m%d%H%M%S'))