aboutsummaryrefslogtreecommitdiff
blob: 04667dbd77be091447781dd5a7b2410f228ea147 (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
# 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