BinpkgFetcher: support selinux PORTAGE_FETCH_T
[portage.git] / pym / _emerge / SpawnProcess.py
1 # Copyright 1999-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from _emerge.SubProcess import SubProcess
5 import sys
6 from portage.cache.mappings import slot_dict_class
7 import portage
8 from portage import _encodings
9 from portage import _unicode_encode
10 from portage import os
11 from portage.const import BASH_BINARY
12 import fcntl
13 import errno
14 import gzip
15
16 class SpawnProcess(SubProcess):
17
18         """
19         Constructor keyword args are passed into portage.process.spawn().
20         The required "args" keyword argument will be passed as the first
21         spawn() argument.
22         """
23
24         _spawn_kwarg_names = ("env", "opt_name", "fd_pipes",
25                 "uid", "gid", "groups", "umask", "logfile",
26                 "path_lookup", "pre_exec")
27
28         __slots__ = ("args",) + \
29                 _spawn_kwarg_names + ("_selinux_type",)
30
31         _file_names = ("log", "process", "stdout")
32         _files_dict = slot_dict_class(_file_names, prefix="")
33
34         def _start(self):
35
36                 if self.cancelled:
37                         return
38
39                 if self.fd_pipes is None:
40                         self.fd_pipes = {}
41                 fd_pipes = self.fd_pipes
42                 fd_pipes.setdefault(0, sys.stdin.fileno())
43                 fd_pipes.setdefault(1, sys.stdout.fileno())
44                 fd_pipes.setdefault(2, sys.stderr.fileno())
45
46                 # flush any pending output
47                 for fd in fd_pipes.values():
48                         if fd == sys.stdout.fileno():
49                                 sys.stdout.flush()
50                         if fd == sys.stderr.fileno():
51                                 sys.stderr.flush()
52
53                 self._files = self._files_dict()
54                 files = self._files
55
56                 master_fd, slave_fd = self._pipe(fd_pipes)
57                 fcntl.fcntl(master_fd, fcntl.F_SETFL,
58                         fcntl.fcntl(master_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
59
60                 logfile = None
61                 if self._can_log(slave_fd):
62                         logfile = self.logfile
63
64                 null_input = None
65                 fd_pipes_orig = fd_pipes.copy()
66                 if self.background:
67                         # TODO: Use job control functions like tcsetpgrp() to control
68                         # access to stdin. Until then, use /dev/null so that any
69                         # attempts to read from stdin will immediately return EOF
70                         # instead of blocking indefinitely.
71                         null_input = open('/dev/null', 'rb')
72                         fd_pipes[0] = null_input.fileno()
73                 else:
74                         fd_pipes[0] = fd_pipes_orig[0]
75
76                 # WARNING: It is very important to use unbuffered mode here,
77                 # in order to avoid issue 5380 with python3.
78                 files.process = os.fdopen(master_fd, 'rb', 0)
79                 if logfile is not None:
80
81                         fd_pipes[1] = slave_fd
82                         fd_pipes[2] = slave_fd
83
84                         files.log = open(_unicode_encode(logfile,
85                                 encoding=_encodings['fs'], errors='strict'), mode='ab')
86                         if logfile.endswith('.gz'):
87                                 files.log = gzip.GzipFile(filename='', mode='ab',
88                                         fileobj=files.log)
89
90                         portage.util.apply_secpass_permissions(logfile,
91                                 uid=portage.portage_uid, gid=portage.portage_gid,
92                                 mode=0o660)
93
94                         if not self.background:
95                                 files.stdout = os.fdopen(os.dup(fd_pipes_orig[1]), 'wb')
96
97                         output_handler = self._output_handler
98
99                 else:
100
101                         # Create a dummy pipe so the scheduler can monitor
102                         # the process from inside a poll() loop.
103                         fd_pipes[self._dummy_pipe_fd] = slave_fd
104                         if self.background:
105                                 fd_pipes[1] = slave_fd
106                                 fd_pipes[2] = slave_fd
107                         output_handler = self._dummy_handler
108
109                 kwargs = {}
110                 for k in self._spawn_kwarg_names:
111                         v = getattr(self, k)
112                         if v is not None:
113                                 kwargs[k] = v
114
115                 kwargs["fd_pipes"] = fd_pipes
116                 kwargs["returnpid"] = True
117                 kwargs.pop("logfile", None)
118
119                 self._reg_id = self.scheduler.register(files.process.fileno(),
120                         self._registered_events, output_handler)
121                 self._registered = True
122
123                 retval = self._spawn(self.args, **kwargs)
124
125                 os.close(slave_fd)
126                 if null_input is not None:
127                         null_input.close()
128
129                 if isinstance(retval, int):
130                         # spawn failed
131                         self._unregister()
132                         self._set_returncode((self.pid, retval))
133                         self.wait()
134                         return
135
136                 self.pid = retval[0]
137                 portage.process.spawned_pids.remove(self.pid)
138
139         def _can_log(self, slave_fd):
140                 return True
141
142         def _pipe(self, fd_pipes):
143                 """
144                 @type fd_pipes: dict
145                 @param fd_pipes: pipes from which to copy terminal size if desired.
146                 """
147                 return os.pipe()
148
149         def _spawn(self, args, **kwargs):
150                 spawn_func = portage.process.spawn
151
152                 if self._selinux_type is not None:
153                         spawn_func = portage.selinux.spawn_wrapper(spawn_func,
154                                 self._selinux_type)
155                         # bash is an allowed entrypoint, while most binaries are not
156                         if args[0] != BASH_BINARY:
157                                 args = [BASH_BINARY, "-c", "exec \"$@\"", args[0]] + args
158
159                 return spawn_func(args, **kwargs)
160
161         def _output_handler(self, fd, event):
162
163                 files = self._files
164                 buf = self._read_buf(files.process, event)
165
166                 if buf is not None:
167
168                         if buf:
169                                 if not self.background:
170                                         write_successful = False
171                                         failures = 0
172                                         while True:
173                                                 try:
174                                                         if not write_successful:
175                                                                 buf.tofile(files.stdout)
176                                                                 write_successful = True
177                                                         files.stdout.flush()
178                                                         break
179                                                 except IOError as e:
180                                                         if e.errno != errno.EAGAIN:
181                                                                 raise
182                                                         del e
183                                                         failures += 1
184                                                         if failures > 50:
185                                                                 # Avoid a potentially infinite loop. In
186                                                                 # most cases, the failure count is zero
187                                                                 # and it's unlikely to exceed 1.
188                                                                 raise
189
190                                                         # This means that a subprocess has put an inherited
191                                                         # stdio file descriptor (typically stdin) into
192                                                         # O_NONBLOCK mode. This is not acceptable (see bug
193                                                         # #264435), so revert it. We need to use a loop
194                                                         # here since there's a race condition due to
195                                                         # parallel processes being able to change the
196                                                         # flags on the inherited file descriptor.
197                                                         # TODO: When possible, avoid having child processes
198                                                         # inherit stdio file descriptors from portage
199                                                         # (maybe it can't be avoided with
200                                                         # PROPERTIES=interactive).
201                                                         fcntl.fcntl(files.stdout.fileno(), fcntl.F_SETFL,
202                                                                 fcntl.fcntl(files.stdout.fileno(),
203                                                                 fcntl.F_GETFL) ^ os.O_NONBLOCK)
204
205                                 try:
206                                         buf.tofile(files.log)
207                                 except TypeError:
208                                         # array.tofile() doesn't work with GzipFile
209                                         files.log.write(buf.tostring())
210                                 files.log.flush()
211                         else:
212                                 self._unregister()
213                                 self.wait()
214
215                 self._unregister_if_appropriate(event)
216
217         def _dummy_handler(self, fd, event):
218                 """
219                 This method is mainly interested in detecting EOF, since
220                 the only purpose of the pipe is to allow the scheduler to
221                 monitor the process from inside a poll() loop.
222                 """
223
224                 buf = self._read_buf(self._files.process, event)
225
226                 if buf is not None:
227
228                         if buf:
229                                 pass
230                         else:
231                                 self._unregister()
232                                 self.wait()
233
234                 self._unregister_if_appropriate(event)
235