summaryrefslogtreecommitdiffstats
path: root/bin/ebuild-ipc.py
blob: 21e5647f3919ef246b953cc8c002b828d8a72880 (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
#!/usr/bin/python
# Copyright 2010 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
#
# This is a helper which ebuild processes can use
# to communicate with portage's main python process.

import array
import logging
import os
import pickle
import select
import signal
import sys
import time

def debug_signal(signum, frame):
	import pdb
	pdb.set_trace()
signal.signal(signal.SIGUSR1, debug_signal)

# Avoid sandbox violations after python upgrade.
pym_path = os.path.join(os.path.dirname(
	os.path.dirname(os.path.realpath(__file__))), "pym")
if os.environ.get("SANDBOX_ON") == "1":
	sandbox_write = os.environ.get("SANDBOX_WRITE", "").split(":")
	if pym_path not in sandbox_write:
		sandbox_write.append(pym_path)
		os.environ["SANDBOX_WRITE"] = \
			":".join(filter(None, sandbox_write))

import portage
portage._disable_legacy_globals()

class EbuildIpc(object):

	_COMMUNICATE_TIMEOUT_SECONDS = 40
	_BUFSIZE = 4096

	def __init__(self):
		self.fifo_dir = os.environ['PORTAGE_BUILDDIR']
		self.ipc_in_fifo = os.path.join(self.fifo_dir, '.ipc_in')
		self.ipc_out_fifo = os.path.join(self.fifo_dir, '.ipc_out')
		self.ipc_lock_file = os.path.join(self.fifo_dir, '.ipc_lock')

	def communicate(self, args):

		# Make locks quiet since unintended locking messages displayed on
		# stdout could corrupt the intended output of this program.
		portage.locks._quiet = True
		lock_obj = portage.locks.lockfile(self.ipc_lock_file, unlinkfile=True)
		start_time = time.time()

		try:
			try:
				portage.exception.AlarmSignal.register(
					self._COMMUNICATE_TIMEOUT_SECONDS)
				returncode = self._communicate(args)
				return returncode
			finally:
				portage.exception.AlarmSignal.unregister()
				portage.locks.unlockfile(lock_obj)
		except portage.exception.AlarmSignal:
			time_elapsed = time.time() - start_time
			portage.util.writemsg_level(
				('ebuild-ipc timed out after %d seconds\n') % \
				(time_elapsed,),
				level=logging.ERROR, noiselevel=-1)
			return 1

	def _communicate(self, args):
		input_fd = os.open(self.ipc_out_fifo, os.O_RDONLY|os.O_NONBLOCK)

		# File streams are in unbuffered mode since we do atomic
		# read and write of whole pickles.
		input_file = os.fdopen(input_fd, 'rb', 0)
		output_file = open(self.ipc_in_fifo, 'wb', 0)

		# Write the whole pickle in a single atomic write() call,
		# since the reader is in non-blocking mode and we want
		# it to get the whole pickle at once.
		output_file.write(pickle.dumps(args))
		output_file.flush()

		events = select.select([input_file], [], [])

		# Read the whole pickle in a single atomic read() call.
		buf = array.array('B')
		try:
			buf.fromfile(input_file, self._BUFSIZE)
		except EOFError as e:
			if not buf:
				portage.util.writemsg("%s\n" % (e,), noiselevel=-1)
		except IOError as e:
			portage.util.writemsg("%s\n" % (e,), noiselevel=-1)

		rval = 2

		if buf:

			try:
				reply = pickle.loads(buf.tostring())
			except (EnvironmentError, EOFError, ValueError,
				pickle.UnpicklingError) as e:
				portage.util.writemsg("%s\n" % (e,), noiselevel=-1)

			else:
				output_file.close()
				input_file.close()

				(out, err, rval) = reply

				if out:
					portage.util.writemsg_stdout(out, noiselevel=-1)

				if err:
					portage.util.writemsg(err, noiselevel=-1)

		return rval

def ebuild_ipc_main(args):
	ebuild_ipc = EbuildIpc()
	return ebuild_ipc.communicate(args)

if __name__ == '__main__':
	sys.exit(ebuild_ipc_main(sys.argv[1:]))