2 # Copyright 2010-2013 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 # This is a helper which ebuild processes can use
6 # to communicate with portage's main python process.
16 def debug_signal(signum, frame):
20 if platform.python_implementation() == 'Jython':
21 debug_signum = signal.SIGUSR2 # bug #424259
23 debug_signum = signal.SIGUSR1
25 signal.signal(debug_signum, debug_signal)
27 # Avoid sandbox violations after python upgrade.
28 pym_path = os.path.join(os.path.dirname(
29 os.path.dirname(os.path.realpath(__file__))), "pym")
30 if os.environ.get("SANDBOX_ON") == "1":
31 sandbox_write = os.environ.get("SANDBOX_WRITE", "").split(":")
32 if pym_path not in sandbox_write:
33 sandbox_write.append(pym_path)
34 os.environ["SANDBOX_WRITE"] = \
35 ":".join(filter(None, sandbox_write))
38 portage._internal_caller = True
39 portage._disable_legacy_globals()
41 from portage.util._async.ForkProcess import ForkProcess
42 from portage.util._eventloop.global_event_loop import global_event_loop
43 from _emerge.PipeReader import PipeReader
45 class FifoWriter(ForkProcess):
47 __slots__ = ('buf', 'fifo',)
50 # Atomically write the whole buffer into the fifo.
51 with open(self.fifo, 'wb', 0) as f:
55 class EbuildIpc(object):
57 # Timeout for each individual communication attempt (we retry
58 # as long as the daemon process appears to be alive).
59 _COMMUNICATE_RETRY_TIMEOUT_MS = 15000
62 self.fifo_dir = os.environ['PORTAGE_BUILDDIR']
63 self.ipc_in_fifo = os.path.join(self.fifo_dir, '.ipc_in')
64 self.ipc_out_fifo = os.path.join(self.fifo_dir, '.ipc_out')
65 self.ipc_lock_file = os.path.join(self.fifo_dir, '.ipc_lock')
67 def _daemon_is_alive(self):
69 builddir_lock = portage.locks.lockfile(self.fifo_dir,
70 wantnewlockfile=True, flags=os.O_NONBLOCK)
71 except portage.exception.TryAgain:
74 portage.locks.unlockfile(builddir_lock)
77 def communicate(self, args):
79 # Make locks quiet since unintended locking messages displayed on
80 # stdout could corrupt the intended output of this program.
81 portage.locks._quiet = True
82 lock_obj = portage.locks.lockfile(self.ipc_lock_file, unlinkfile=True)
85 return self._communicate(args)
87 portage.locks.unlockfile(lock_obj)
89 def _timeout_retry_msg(self, start_time, when):
90 time_elapsed = time.time() - start_time
91 portage.util.writemsg_level(
92 portage.localization._(
93 'ebuild-ipc timed out %s after %d seconds,' + \
94 ' retrying...\n') % (when, time_elapsed),
95 level=logging.ERROR, noiselevel=-1)
97 def _no_daemon_msg(self):
98 portage.util.writemsg_level(
99 portage.localization._(
100 'ebuild-ipc: daemon process not detected\n'),
101 level=logging.ERROR, noiselevel=-1)
103 def _run_writer(self, fifo_writer, msg):
105 Wait on pid and return an appropriate exit code. This
106 may return unsuccessfully due to timeout if the daemon
107 process does not appear to be alive.
110 start_time = time.time()
113 eof = fifo_writer.poll() is not None
116 fifo_writer._wait_loop(timeout=self._COMMUNICATE_RETRY_TIMEOUT_MS)
118 eof = fifo_writer.poll() is not None
121 elif self._daemon_is_alive():
122 self._timeout_retry_msg(start_time, msg)
125 self._no_daemon_msg()
129 return fifo_writer.wait()
131 def _receive_reply(self, input_fd):
133 start_time = time.time()
135 pipe_reader = PipeReader(input_files={"input_fd":input_fd},
136 scheduler=global_event_loop())
139 eof = pipe_reader.poll() is not None
142 pipe_reader._wait_loop(timeout=self._COMMUNICATE_RETRY_TIMEOUT_MS)
143 eof = pipe_reader.poll() is not None
145 if self._daemon_is_alive():
146 self._timeout_retry_msg(start_time,
147 portage.localization._('during read'))
150 self._no_daemon_msg()
153 buf = pipe_reader.getvalue()
159 portage.util.writemsg_level(
160 "ebuild-ipc: %s\n" % \
161 (portage.localization._('read failed'),),
162 level=logging.ERROR, noiselevel=-1)
167 reply = pickle.loads(buf)
170 except Exception as e:
171 # The pickle module can raise practically
172 # any exception when given corrupt data.
173 portage.util.writemsg_level(
174 "ebuild-ipc: %s\n" % (e,),
175 level=logging.ERROR, noiselevel=-1)
179 (out, err, retval) = reply
182 portage.util.writemsg_stdout(out, noiselevel=-1)
185 portage.util.writemsg(err, noiselevel=-1)
189 def _communicate(self, args):
191 if not self._daemon_is_alive():
192 self._no_daemon_msg()
195 # Open the input fifo before the output fifo, in order to make it
196 # possible for the daemon to send a reply without blocking. This
197 # improves performance, and also makes it possible for the daemon
198 # to do a non-blocking write without a race condition.
199 input_fd = os.open(self.ipc_out_fifo,
200 os.O_RDONLY|os.O_NONBLOCK)
202 # Use forks so that the child process can handle blocking IO
203 # un-interrupted, while the parent handles all timeout
204 # considerations. This helps to avoid possible race conditions
205 # from interference between timeouts and blocking IO operations.
206 msg = portage.localization._('during write')
207 retval = self._run_writer(FifoWriter(buf=pickle.dumps(args),
208 fifo=self.ipc_in_fifo, scheduler=global_event_loop()), msg)
210 if retval != os.EX_OK:
211 portage.util.writemsg_level(
212 "ebuild-ipc: %s: %s\n" % (msg,
213 portage.localization._('subprocess failure: %s') % \
214 retval), level=logging.ERROR, noiselevel=-1)
217 if not self._daemon_is_alive():
218 self._no_daemon_msg()
221 return self._receive_reply(input_fd)
223 def ebuild_ipc_main(args):
224 ebuild_ipc = EbuildIpc()
225 return ebuild_ipc.communicate(args)
227 if __name__ == '__main__':
228 sys.exit(ebuild_ipc_main(sys.argv[1:]))