Fix some typos.
[portage.git] / pym / _emerge / AbstractEbuildProcess.py
1 # Copyright 1999-2012 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 import io
5 import platform
6 import stat
7 import subprocess
8 import textwrap
9 from _emerge.SpawnProcess import SpawnProcess
10 from _emerge.EbuildBuildDir import EbuildBuildDir
11 from _emerge.EbuildIpcDaemon import EbuildIpcDaemon
12 import portage
13 from portage.elog import messages as elog_messages
14 from portage.localization import _
15 from portage.package.ebuild._ipc.ExitCommand import ExitCommand
16 from portage.package.ebuild._ipc.QueryCommand import QueryCommand
17 from portage import os
18 from portage.util._pty import _create_pty_or_pipe
19 from portage.util import apply_secpass_permissions
20
21 class AbstractEbuildProcess(SpawnProcess):
22
23         __slots__ = ('phase', 'settings',) + \
24                 ('_build_dir', '_ipc_daemon', '_exit_command', '_exit_timeout_id')
25
26         _phases_without_builddir = ('clean', 'cleanrm', 'depend', 'help',)
27         _phases_interactive_whitelist = ('config',)
28         _phases_without_cgroup = ('preinst', 'postinst', 'prerm', 'postrm', 'config')
29
30         # Number of milliseconds to allow natural exit of the ebuild
31         # process after it has called the exit command via IPC. It
32         # doesn't hurt to be generous here since the scheduler
33         # continues to process events during this period, and it can
34         # return long before the timeout expires.
35         _exit_timeout = 10000 # 10 seconds
36
37         # The EbuildIpcDaemon support is well tested, but this variable
38         # is left so we can temporarily disable it if any issues arise.
39         _enable_ipc_daemon = True
40
41         def __init__(self, **kwargs):
42                 SpawnProcess.__init__(self, **kwargs)
43                 if self.phase is None:
44                         phase = self.settings.get("EBUILD_PHASE")
45                         if not phase:
46                                 phase = 'other'
47                         self.phase = phase
48
49         def _start(self):
50
51                 need_builddir = self.phase not in self._phases_without_builddir
52
53                 # This can happen if the pre-clean phase triggers
54                 # die_hooks for some reason, and PORTAGE_BUILDDIR
55                 # doesn't exist yet.
56                 if need_builddir and \
57                         not os.path.isdir(self.settings['PORTAGE_BUILDDIR']):
58                         msg = _("The ebuild phase '%s' has been aborted "
59                         "since PORTAGE_BUILDDIR does not exist: '%s'") % \
60                         (self.phase, self.settings['PORTAGE_BUILDDIR'])
61                         self._eerror(textwrap.wrap(msg, 72))
62                         self._set_returncode((self.pid, 1 << 8))
63                         self._async_wait()
64                         return
65
66                 # Check if the cgroup hierarchy is in place. If it's not, mount it.
67                 if (os.geteuid() == 0 and platform.system() == 'Linux'
68                                 and 'cgroup' in self.settings.features
69                                 and self.phase not in self._phases_without_cgroup):
70                         cgroup_root = '/sys/fs/cgroup'
71                         cgroup_portage = os.path.join(cgroup_root, 'portage')
72                         cgroup_path = os.path.join(cgroup_portage,
73                                         '%s:%s' % (self.settings["CATEGORY"],
74                                                 self.settings["PF"]))
75                         try:
76                                 # cgroup tmpfs
77                                 if not os.path.ismount(cgroup_root):
78                                         # we expect /sys/fs to be there already
79                                         if not os.path.isdir(cgroup_root):
80                                                 os.mkdir(cgroup_root, 0o755)
81                                         subprocess.check_call(['mount', '-t', 'tmpfs',
82                                                 '-o', 'rw,nosuid,nodev,noexec,mode=0755',
83                                                 'tmpfs', cgroup_root])
84
85                                 # portage subsystem
86                                 if not os.path.ismount(cgroup_portage):
87                                         if not os.path.isdir(cgroup_portage):
88                                                 os.mkdir(cgroup_portage, 0o755)
89                                         subprocess.check_call(['mount', '-t', 'cgroup',
90                                                 '-o', 'rw,nosuid,nodev,noexec,none,name=portage',
91                                                 'tmpfs', cgroup_portage])
92
93                                 # the ebuild cgroup
94                                 if not os.path.isdir(cgroup_path):
95                                         os.mkdir(cgroup_path)
96                         except (subprocess.CalledProcessError, OSError):
97                                 pass
98                         else:
99                                 self.cgroup = cgroup_path
100
101                 if self.background:
102                         # Automatically prevent color codes from showing up in logs,
103                         # since we're not displaying to a terminal anyway.
104                         self.settings['NOCOLOR'] = 'true'
105
106                 if self._enable_ipc_daemon:
107                         self.settings.pop('PORTAGE_EBUILD_EXIT_FILE', None)
108                         if self.phase not in self._phases_without_builddir:
109                                 if 'PORTAGE_BUILDDIR_LOCKED' not in self.settings:
110                                         self._build_dir = EbuildBuildDir(
111                                                 scheduler=self.scheduler, settings=self.settings)
112                                         self._build_dir.lock()
113                                 self.settings['PORTAGE_IPC_DAEMON'] = "1"
114                                 self._start_ipc_daemon()
115                         else:
116                                 self.settings.pop('PORTAGE_IPC_DAEMON', None)
117                 else:
118                         # Since the IPC daemon is disabled, use a simple tempfile based
119                         # approach to detect unexpected exit like in bug #190128.
120                         self.settings.pop('PORTAGE_IPC_DAEMON', None)
121                         if self.phase not in self._phases_without_builddir:
122                                 exit_file = os.path.join(
123                                         self.settings['PORTAGE_BUILDDIR'],
124                                         '.exit_status')
125                                 self.settings['PORTAGE_EBUILD_EXIT_FILE'] = exit_file
126                                 try:
127                                         os.unlink(exit_file)
128                                 except OSError:
129                                         if os.path.exists(exit_file):
130                                                 # make sure it doesn't exist
131                                                 raise
132                         else:
133                                 self.settings.pop('PORTAGE_EBUILD_EXIT_FILE', None)
134
135                 if self.fd_pipes is None:
136                         self.fd_pipes = {}
137                 null_fd = None
138                 if 0 not in self.fd_pipes and \
139                         self.phase not in self._phases_interactive_whitelist and \
140                         "interactive" not in self.settings.get("PROPERTIES", "").split():
141                         null_fd = os.open('/dev/null', os.O_RDONLY)
142                         self.fd_pipes[0] = null_fd
143
144                 try:
145                         SpawnProcess._start(self)
146                 finally:
147                         if null_fd is not None:
148                                 os.close(null_fd)
149
150         def _init_ipc_fifos(self):
151
152                 input_fifo = os.path.join(
153                         self.settings['PORTAGE_BUILDDIR'], '.ipc_in')
154                 output_fifo = os.path.join(
155                         self.settings['PORTAGE_BUILDDIR'], '.ipc_out')
156
157                 for p in (input_fifo, output_fifo):
158
159                         st = None
160                         try:
161                                 st = os.lstat(p)
162                         except OSError:
163                                 os.mkfifo(p)
164                         else:
165                                 if not stat.S_ISFIFO(st.st_mode):
166                                         st = None
167                                         try:
168                                                 os.unlink(p)
169                                         except OSError:
170                                                 pass
171                                         os.mkfifo(p)
172
173                         apply_secpass_permissions(p,
174                                 uid=os.getuid(),
175                                 gid=portage.data.portage_gid,
176                                 mode=0o770, stat_cached=st)
177
178                 return (input_fifo, output_fifo)
179
180         def _start_ipc_daemon(self):
181                 self._exit_command = ExitCommand()
182                 self._exit_command.reply_hook = self._exit_command_callback
183                 query_command = QueryCommand(self.settings, self.phase)
184                 commands = {
185                         'available_eclasses'  : query_command,
186                         'best_version'        : query_command,
187                         'eclass_path'         : query_command,
188                         'exit'                : self._exit_command,
189                         'has_version'         : query_command,
190                         'license_path'        : query_command,
191                         'master_repositories' : query_command,
192                         'repository_path'     : query_command,
193                 }
194                 input_fifo, output_fifo = self._init_ipc_fifos()
195                 self._ipc_daemon = EbuildIpcDaemon(commands=commands,
196                         input_fifo=input_fifo,
197                         output_fifo=output_fifo,
198                         scheduler=self.scheduler)
199                 self._ipc_daemon.start()
200
201         def _exit_command_callback(self):
202                 if self._registered:
203                         # Let the process exit naturally, if possible.
204                         self._exit_timeout_id = \
205                                 self.scheduler.timeout_add(self._exit_timeout,
206                                 self._exit_command_timeout_cb)
207
208         def _exit_command_timeout_cb(self):
209                 if self._registered:
210                         # If it doesn't exit naturally in a reasonable amount
211                         # of time, kill it (solves bug #278895). We try to avoid
212                         # this when possible since it makes sandbox complain about
213                         # being killed by a signal.
214                         self.cancel()
215                         self._exit_timeout_id = \
216                                 self.scheduler.timeout_add(self._cancel_timeout,
217                                         self._cancel_timeout_cb)
218                 else:
219                         self._exit_timeout_id = None
220
221                 return False # only run once
222
223         def _cancel_timeout_cb(self):
224                 self._exit_timeout_id = None
225                 self.wait()
226                 return False # only run once
227
228         def _orphan_process_warn(self):
229                 phase = self.phase
230
231                 msg = _("The ebuild phase '%s' with pid %s appears "
232                 "to have left an orphan process running in the "
233                 "background.") % (phase, self.pid)
234
235                 self._eerror(textwrap.wrap(msg, 72))
236
237         def _pipe(self, fd_pipes):
238                 stdout_pipe = None
239                 if not self.background:
240                         stdout_pipe = fd_pipes.get(1)
241                 got_pty, master_fd, slave_fd = \
242                         _create_pty_or_pipe(copy_term_size=stdout_pipe)
243                 return (master_fd, slave_fd)
244
245         def _can_log(self, slave_fd):
246                 # With sesandbox, logging works through a pty but not through a
247                 # normal pipe. So, disable logging if ptys are broken.
248                 # See Bug #162404.
249                 # TODO: Add support for logging via named pipe (fifo) with
250                 # sesandbox, since EbuildIpcDaemon uses a fifo and it's known
251                 # to be compatible with sesandbox.
252                 return not ('sesandbox' in self.settings.features \
253                         and self.settings.selinux_enabled()) or os.isatty(slave_fd)
254
255         def _killed_by_signal(self, signum):
256                 msg = _("The ebuild phase '%s' has been "
257                 "killed by signal %s.") % (self.phase, signum)
258                 self._eerror(textwrap.wrap(msg, 72))
259
260         def _unexpected_exit(self):
261
262                 phase = self.phase
263
264                 msg = _("The ebuild phase '%s' has exited "
265                 "unexpectedly. This type of behavior "
266                 "is known to be triggered "
267                 "by things such as failed variable "
268                 "assignments (bug #190128) or bad substitution "
269                 "errors (bug #200313). Normally, before exiting, bash should "
270                 "have displayed an error message above. If bash did not "
271                 "produce an error message above, it's possible "
272                 "that the ebuild has called `exit` when it "
273                 "should have called `die` instead. This behavior may also "
274                 "be triggered by a corrupt bash binary or a hardware "
275                 "problem such as memory or cpu malfunction. If the problem is not "
276                 "reproducible or it appears to occur randomly, then it is likely "
277                 "to be triggered by a hardware problem. "
278                 "If you suspect a hardware problem then you should "
279                 "try some basic hardware diagnostics such as memtest. "
280                 "Please do not report this as a bug unless it is consistently "
281                 "reproducible and you are sure that your bash binary and hardware "
282                 "are functioning properly.") % phase
283
284                 self._eerror(textwrap.wrap(msg, 72))
285
286         def _eerror(self, lines):
287                 self._elog('eerror', lines)
288
289         def _elog(self, elog_funcname, lines):
290                 out = io.StringIO()
291                 phase = self.phase
292                 elog_func = getattr(elog_messages, elog_funcname)
293                 global_havecolor = portage.output.havecolor
294                 try:
295                         portage.output.havecolor = \
296                                 self.settings.get('NOCOLOR', 'false').lower() in ('no', 'false')
297                         for line in lines:
298                                 elog_func(line, phase=phase, key=self.settings.mycpv, out=out)
299                 finally:
300                         portage.output.havecolor = global_havecolor
301                 msg = out.getvalue()
302                 if msg:
303                         log_path = None
304                         if self.settings.get("PORTAGE_BACKGROUND") != "subprocess":
305                                 log_path = self.settings.get("PORTAGE_LOG_FILE")
306                         self.scheduler.output(msg, log_path=log_path)
307
308         def _log_poll_exception(self, event):
309                 self._elog("eerror",
310                         ["%s received strange poll event: %s\n" % \
311                         (self.__class__.__name__, event,)])
312
313         def _set_returncode(self, wait_retval):
314                 SpawnProcess._set_returncode(self, wait_retval)
315
316                 if self._exit_timeout_id is not None:
317                         self.scheduler.source_remove(self._exit_timeout_id)
318                         self._exit_timeout_id = None
319
320                 if self._ipc_daemon is not None:
321                         self._ipc_daemon.cancel()
322                         if self._exit_command.exitcode is not None:
323                                 self.returncode = self._exit_command.exitcode
324                         else:
325                                 if self.returncode < 0:
326                                         if not self.cancelled:
327                                                 self._killed_by_signal(-self.returncode)
328                                 else:
329                                         self.returncode = 1
330                                         if not self.cancelled:
331                                                 self._unexpected_exit()
332                         if self._build_dir is not None:
333                                 self._build_dir.unlock()
334                                 self._build_dir = None
335                 elif not self.cancelled:
336                         exit_file = self.settings.get('PORTAGE_EBUILD_EXIT_FILE')
337                         if exit_file and not os.path.exists(exit_file):
338                                 if self.returncode < 0:
339                                         if not self.cancelled:
340                                                 self._killed_by_signal(-self.returncode)
341                                 else:
342                                         self.returncode = 1
343                                         if not self.cancelled:
344                                                 self._unexpected_exit()