1 # Copyright 1999-2012 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
9 from _emerge.SpawnProcess import SpawnProcess
10 from _emerge.EbuildBuildDir import EbuildBuildDir
11 from _emerge.EbuildIpcDaemon import EbuildIpcDaemon
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
21 class AbstractEbuildProcess(SpawnProcess):
23 __slots__ = ('phase', 'settings',) + \
24 ('_build_dir', '_ipc_daemon', '_exit_command', '_exit_timeout_id')
26 _phases_without_builddir = ('clean', 'cleanrm', 'depend', 'help',)
27 _phases_interactive_whitelist = ('config',)
28 _phases_without_cgroup = ('preinst', 'postinst', 'prerm', 'postrm', 'config')
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
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
41 def __init__(self, **kwargs):
42 SpawnProcess.__init__(self, **kwargs)
43 if self.phase is None:
44 phase = self.settings.get("EBUILD_PHASE")
51 need_builddir = self.phase not in self._phases_without_builddir
53 # This can happen if the pre-clean phase triggers
54 # die_hooks for some reason, and PORTAGE_BUILDDIR
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))
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"],
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])
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])
94 if not os.path.isdir(cgroup_path):
96 except (subprocess.CalledProcessError, OSError):
99 self.cgroup = cgroup_path
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'
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()
116 self.settings.pop('PORTAGE_IPC_DAEMON', None)
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'],
125 self.settings['PORTAGE_EBUILD_EXIT_FILE'] = exit_file
129 if os.path.exists(exit_file):
130 # make sure it doesn't exist
133 self.settings.pop('PORTAGE_EBUILD_EXIT_FILE', None)
135 if self.fd_pipes is 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
145 SpawnProcess._start(self)
147 if null_fd is not None:
150 def _init_ipc_fifos(self):
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')
157 for p in (input_fifo, output_fifo):
165 if not stat.S_ISFIFO(st.st_mode):
173 apply_secpass_permissions(p,
175 gid=portage.data.portage_gid,
176 mode=0o770, stat_cached=st)
178 return (input_fifo, output_fifo)
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)
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,
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()
201 def _exit_command_callback(self):
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)
208 def _exit_command_timeout_cb(self):
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.
215 self._exit_timeout_id = \
216 self.scheduler.timeout_add(self._cancel_timeout,
217 self._cancel_timeout_cb)
219 self._exit_timeout_id = None
221 return False # only run once
223 def _cancel_timeout_cb(self):
224 self._exit_timeout_id = None
226 return False # only run once
228 def _orphan_process_warn(self):
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)
235 self._eerror(textwrap.wrap(msg, 72))
237 def _pipe(self, fd_pipes):
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)
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.
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)
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))
260 def _unexpected_exit(self):
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
284 self._eerror(textwrap.wrap(msg, 72))
286 def _eerror(self, lines):
287 self._elog('eerror', lines)
289 def _elog(self, elog_funcname, lines):
292 elog_func = getattr(elog_messages, elog_funcname)
293 global_havecolor = portage.output.havecolor
295 portage.output.havecolor = \
296 self.settings.get('NOCOLOR', 'false').lower() in ('no', 'false')
298 elog_func(line, phase=phase, key=self.settings.mycpv, out=out)
300 portage.output.havecolor = global_havecolor
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)
308 def _log_poll_exception(self, event):
310 ["%s received strange poll event: %s\n" % \
311 (self.__class__.__name__, event,)])
313 def _set_returncode(self, wait_retval):
314 SpawnProcess._set_returncode(self, wait_retval)
316 if self._exit_timeout_id is not None:
317 self.scheduler.source_remove(self._exit_timeout_id)
318 self._exit_timeout_id = None
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
325 if self.returncode < 0:
326 if not self.cancelled:
327 self._killed_by_signal(-self.returncode)
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)
343 if not self.cancelled:
344 self._unexpected_exit()