aboutsummaryrefslogtreecommitdiff
blob: 78f98d2b5783b3f30de9cae9cf82c9d917d1dfbb (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
159
160
161
162
163
# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import functools

from _emerge.AsynchronousLock import AsynchronousLock

import portage
from portage import os
from portage.exception import PortageException
from portage.util.SlotObject import SlotObject


class EbuildBuildDir(SlotObject):

    __slots__ = ("scheduler", "settings", "locked", "_catdir", "_lock_obj")

    def __init__(self, **kwargs):
        SlotObject.__init__(self, **kwargs)
        self.locked = False

    def _assert_lock(self, async_lock):
        if async_lock.returncode != os.EX_OK:
            # TODO: create a better way to propagate this error to the caller
            raise AssertionError(
                "AsynchronousLock failed with returncode %s" % (async_lock.returncode,)
            )

    def clean_log(self):
        """Discard existing log. The log will not be be discarded
        in cases when it would not make sense, like when FEATURES=keepwork
        is enabled."""
        settings = self.settings
        if "keepwork" in settings.features:
            return
        log_file = settings.get("PORTAGE_LOG_FILE")
        if log_file is not None and os.path.isfile(log_file):
            try:
                os.unlink(log_file)
            except OSError:
                pass

    def async_lock(self):
        """
        Acquire the lock asynchronously. Notification is available
        via the add_done_callback method of the returned Future instance.

        This raises an AlreadyLocked exception if async_lock() is called
        while a lock is already held. In order to avoid this, call
        async_unlock() or check whether the "locked" attribute is True
        or False before calling async_lock().

        @returns: Future, result is None
        """
        if self._lock_obj is not None:
            raise self.AlreadyLocked((self._lock_obj,))

        dir_path = self.settings.get("PORTAGE_BUILDDIR")
        if not dir_path:
            raise AssertionError("PORTAGE_BUILDDIR is unset")
        catdir = os.path.dirname(dir_path)
        self._catdir = catdir
        catdir_lock = AsynchronousLock(path=catdir, scheduler=self.scheduler)
        builddir_lock = AsynchronousLock(path=dir_path, scheduler=self.scheduler)
        result = self.scheduler.create_future()

        def catdir_locked(catdir_lock):
            try:
                self._assert_lock(catdir_lock)
            except AssertionError as e:
                result.set_exception(e)
                return

            try:
                portage.util.ensure_dirs(
                    catdir, gid=portage.portage_gid, mode=0o70, mask=0
                )
            except PortageException as e:
                if not os.path.isdir(catdir):
                    result.set_exception(e)
                    return

            builddir_lock.addExitListener(builddir_locked)
            builddir_lock.start()

        def builddir_locked(builddir_lock):
            try:
                self._assert_lock(builddir_lock)
            except AssertionError as e:
                catdir_lock.async_unlock.add_done_callback(
                    functools.partial(catdir_unlocked, exception=e)
                )
                return

            self._lock_obj = builddir_lock
            self.locked = True
            self.settings["PORTAGE_BUILDDIR_LOCKED"] = "1"
            catdir_lock.async_unlock().add_done_callback(catdir_unlocked)

        def catdir_unlocked(future, exception=None):
            if not (exception is None and future.exception() is None):
                result.set_exception(exception or future.exception())
            else:
                result.set_result(None)

        try:
            portage.util.ensure_dirs(
                os.path.dirname(catdir), gid=portage.portage_gid, mode=0o70, mask=0
            )
        except PortageException:
            if not os.path.isdir(os.path.dirname(catdir)):
                raise

        catdir_lock.addExitListener(catdir_locked)
        catdir_lock.start()
        return result

    def async_unlock(self):
        """
        Release the lock asynchronously. Release notification is available
        via the add_done_callback method of the returned Future instance.

        @returns: Future, result is None
        """
        result = self.scheduler.create_future()

        def builddir_unlocked(future):
            if future.exception() is not None:
                result.set_exception(future.exception())
            else:
                self._lock_obj = None
                self.locked = False
                self.settings.pop("PORTAGE_BUILDDIR_LOCKED", None)
                catdir_lock = AsynchronousLock(
                    path=self._catdir, scheduler=self.scheduler
                )
                catdir_lock.addExitListener(catdir_locked)
                catdir_lock.start()

        def catdir_locked(catdir_lock):
            if catdir_lock.wait() != os.EX_OK:
                result.set_result(None)
            else:
                try:
                    os.rmdir(self._catdir)
                except OSError:
                    pass
                catdir_lock.async_unlock().add_done_callback(catdir_unlocked)

        def catdir_unlocked(future):
            if future.exception() is None:
                result.set_result(None)
            else:
                result.set_exception(future.exception())

        if self._lock_obj is None:
            self.scheduler.call_soon(result.set_result, None)
        else:
            self._lock_obj.async_unlock().add_done_callback(builddir_unlocked)
        return result

    class AlreadyLocked(portage.exception.PortageException):
        pass