Enable BytesWarnings.
[portage.git] / bin / ebuild-ipc.py
1 #!/usr/bin/python -bb
2 # Copyright 2010-2014 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4 #
5 # This is a helper which ebuild processes can use
6 # to communicate with portage's main python process.
7
8 import logging
9 import os
10 import pickle
11 import platform
12 import signal
13 import sys
14 import time
15
16 def debug_signal(signum, frame):
17         import pdb
18         pdb.set_trace()
19
20 if platform.python_implementation() == 'Jython':
21         debug_signum = signal.SIGUSR2 # bug #424259
22 else:
23         debug_signum = signal.SIGUSR1
24
25 signal.signal(debug_signum, debug_signal)
26
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))
36
37 import portage
38 portage._internal_caller = True
39 portage._disable_legacy_globals()
40
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
44
45 class FifoWriter(ForkProcess):
46
47         __slots__ = ('buf', 'fifo',)
48
49         def _run(self):
50                 # Atomically write the whole buffer into the fifo.
51                 with open(self.fifo, 'wb', 0) as f:
52                         f.write(self.buf)
53                 return os.EX_OK
54
55 class EbuildIpc(object):
56
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
60
61         def __init__(self):
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')
66
67         def _daemon_is_alive(self):
68                 try:
69                         builddir_lock = portage.locks.lockfile(self.fifo_dir,
70                                 wantnewlockfile=True, flags=os.O_NONBLOCK)
71                 except portage.exception.TryAgain:
72                         return True
73                 else:
74                         portage.locks.unlockfile(builddir_lock)
75                         return False
76
77         def communicate(self, args):
78
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)
83
84                 try:
85                         return self._communicate(args)
86                 finally:
87                         portage.locks.unlockfile(lock_obj)
88
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)
96
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)
102
103         def _run_writer(self, fifo_writer, msg):
104                 """
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.
108                 """
109
110                 start_time = time.time()
111
112                 fifo_writer.start()
113                 eof = fifo_writer.poll() is not None
114
115                 while not eof:
116                         fifo_writer._wait_loop(timeout=self._COMMUNICATE_RETRY_TIMEOUT_MS)
117
118                         eof = fifo_writer.poll() is not None
119                         if eof:
120                                 break
121                         elif self._daemon_is_alive():
122                                 self._timeout_retry_msg(start_time, msg)
123                         else:
124                                 fifo_writer.cancel()
125                                 self._no_daemon_msg()
126                                 fifo_writer.wait()
127                                 return 2
128
129                 return fifo_writer.wait()
130
131         def _receive_reply(self, input_fd):
132
133                 start_time = time.time()
134
135                 pipe_reader = PipeReader(input_files={"input_fd":input_fd},
136                         scheduler=global_event_loop())
137                 pipe_reader.start()
138
139                 eof = pipe_reader.poll() is not None
140
141                 while not eof:
142                         pipe_reader._wait_loop(timeout=self._COMMUNICATE_RETRY_TIMEOUT_MS)
143                         eof = pipe_reader.poll() is not None
144                         if not eof:
145                                 if self._daemon_is_alive():
146                                         self._timeout_retry_msg(start_time,
147                                                 portage.localization._('during read'))
148                                 else:
149                                         pipe_reader.cancel()
150                                         self._no_daemon_msg()
151                                         return 2
152
153                 buf = pipe_reader.getvalue()
154
155                 retval = 2
156
157                 if not buf:
158
159                         portage.util.writemsg_level(
160                                 "ebuild-ipc: %s\n" % \
161                                 (portage.localization._('read failed'),),
162                                 level=logging.ERROR, noiselevel=-1)
163
164                 else:
165
166                         try:
167                                 reply = pickle.loads(buf)
168                         except SystemExit:
169                                 raise
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)
176
177                         else:
178
179                                 (out, err, retval) = reply
180
181                                 if out:
182                                         portage.util.writemsg_stdout(out, noiselevel=-1)
183
184                                 if err:
185                                         portage.util.writemsg(err, noiselevel=-1)
186
187                 return retval
188
189         def _communicate(self, args):
190
191                 if not self._daemon_is_alive():
192                         self._no_daemon_msg()
193                         return 2
194
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)
201
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)
209
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)
215                         return retval
216
217                 if not self._daemon_is_alive():
218                         self._no_daemon_msg()
219                         return 2
220
221                 return self._receive_reply(input_fd)
222
223 def ebuild_ipc_main(args):
224         ebuild_ipc = EbuildIpc()
225         return ebuild_ipc.communicate(args)
226
227 if __name__ == '__main__':
228         sys.exit(ebuild_ipc_main(sys.argv[1:]))