1 # portage.py -- core Portage functionality
2 # Copyright 1998-2009 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
12 from portage import os
13 from portage import _encodings
14 from portage import _unicode_decode
15 from portage import _unicode_encode
17 portage.proxy.lazyimport.lazyimport(globals(),
18 'portage.util:dump_traceback',
21 from portage.const import BASH_BINARY, SANDBOX_BINARY, FAKEROOT_BINARY
22 from portage.exception import CommandNotFound
26 max_fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
30 if sys.hexversion >= 0x3000000:
33 if os.path.isdir("/proc/%i/fd" % os.getpid()):
35 return (int(fd) for fd in os.listdir("/proc/%i/fd" % os.getpid()) \
39 return range(max_fd_limit)
41 sandbox_capable = (os.path.isfile(SANDBOX_BINARY) and
42 os.access(SANDBOX_BINARY, os.X_OK))
44 fakeroot_capable = (os.path.isfile(FAKEROOT_BINARY) and
45 os.access(FAKEROOT_BINARY, os.X_OK))
47 def spawn_bash(mycommand, debug=False, opt_name=None, **keywords):
49 Spawns a bash shell running a specific commands
51 @param mycommand: The command for bash to run
52 @type mycommand: String
53 @param debug: Turn bash debugging on (set -x)
55 @param opt_name: Name of the spawned process (detaults to binary name)
56 @type opt_name: String
57 @param keywords: Extra Dictionary arguments to pass to spawn
58 @type keywords: Dictionary
63 opt_name = os.path.basename(mycommand.split()[0])
65 # Print commands and their arguments as they are executed.
68 args.append(mycommand)
69 return spawn(args, opt_name=opt_name, **keywords)
71 def spawn_sandbox(mycommand, opt_name=None, **keywords):
72 if not sandbox_capable:
73 return spawn_bash(mycommand, opt_name=opt_name, **keywords)
76 opt_name = os.path.basename(mycommand.split()[0])
77 args.append(mycommand)
78 return spawn(args, opt_name=opt_name, **keywords)
80 def spawn_fakeroot(mycommand, fakeroot_state=None, opt_name=None, **keywords):
81 args=[FAKEROOT_BINARY]
83 opt_name = os.path.basename(mycommand.split()[0])
85 open(fakeroot_state, "a").close()
87 args.append(fakeroot_state)
89 args.append(fakeroot_state)
91 args.append(BASH_BINARY)
93 args.append(mycommand)
94 return spawn(args, opt_name=opt_name, **keywords)
97 def atexit_register(func, *args, **kargs):
98 """Wrapper around atexit.register that is needed in order to track
99 what is registered. For example, when portage restarts itself via
100 os.execv, the atexit module does not work so we have to do it
101 manually by calling the run_exitfuncs() function in this module."""
102 _exithandlers.append((func, args, kargs))
105 """This should behave identically to the routine performed by
106 the atexit module at exit time. It's only necessary to call this
107 function when atexit will not work (because of os.execv, for
110 # This function is a copy of the private atexit._run_exitfuncs()
111 # from the python 2.4.2 sources. The only difference from the
112 # original function is in the output to stderr.
115 func, targs, kargs = _exithandlers.pop()
117 func(*targs, **kargs)
119 exc_info = sys.exc_info()
120 except: # No idea what they called, so we need this broad except here.
121 dump_traceback("Error in portage.process.run_exitfuncs", noiselevel=0)
122 exc_info = sys.exc_info()
124 if exc_info is not None:
125 if sys.hexversion >= 0x3000000:
126 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
128 exec("raise exc_info[0], exc_info[1], exc_info[2]")
130 atexit.register(run_exitfuncs)
132 # We need to make sure that any processes spawned are killed off when
133 # we exit. spawn() takes care of adding and removing pids to this list
134 # as it creates and cleans up processes.
138 pid = spawned_pids.pop()
140 if os.waitpid(pid, os.WNOHANG) == (0, 0):
141 os.kill(pid, signal.SIGTERM)
144 # This pid has been cleaned up outside
148 atexit_register(cleanup)
150 def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
151 uid=None, gid=None, groups=None, umask=None, logfile=None,
152 path_lookup=True, pre_exec=None):
154 Spawns a given command.
156 @param mycommand: the command to execute
157 @type mycommand: String or List (Popen style list)
158 @param env: A dict of Key=Value pairs for env variables
159 @type env: Dictionary
160 @param opt_name: an optional name for the spawn'd process (defaults to the binary name)
161 @type opt_name: String
162 @param fd_pipes: A dict of mapping for pipes, { '0': stdin, '1': stdout } for example
163 @type fd_pipes: Dictionary
164 @param returnpid: Return the Process IDs for a successful spawn.
165 NOTE: This requires the caller clean up all the PIDs, otherwise spawn will clean them.
166 @type returnpid: Boolean
167 @param uid: User ID to spawn as; useful for dropping privilages
169 @param gid: Group ID to spawn as; useful for dropping privilages
171 @param groups: Group ID's to spawn in: useful for having the process run in multiple group contexts.
173 @param umask: An integer representing the umask for the process (see man chmod for umask details)
175 @param logfile: name of a file to use for logging purposes
176 @type logfile: String
177 @param path_lookup: If the binary is not fully specified then look for it in PATH
178 @type path_lookup: Boolean
179 @param pre_exec: A function to be called with no arguments just prior to the exec call.
180 @type pre_exec: callable
182 logfile requires stdout and stderr to be assigned to this process (ie not pointed
187 # mycommand is either a str or a list
188 if isinstance(mycommand, basestring):
189 mycommand = mycommand.split()
191 if sys.hexversion < 0x3000000:
192 # Avoid a potential UnicodeEncodeError from os.execve().
194 for k, v in env.items():
195 env_bytes[_unicode_encode(k, encoding=_encodings['content'])] = \
196 _unicode_encode(v, encoding=_encodings['content'])
200 # If an absolute path to an executable file isn't given
201 # search for it unless we've been told not to.
202 binary = mycommand[0]
203 if binary not in (BASH_BINARY, SANDBOX_BINARY, FAKEROOT_BINARY) and \
204 (not os.path.isabs(binary) or not os.path.isfile(binary)
205 or not os.access(binary, os.X_OK)):
206 binary = path_lookup and find_binary(binary) or None
208 raise CommandNotFound(mycommand[0])
210 # If we haven't been told what file descriptors to use
211 # default to propogating our stdin, stdout and stderr.
214 0:sys.stdin.fileno(),
215 1:sys.stdout.fileno(),
216 2:sys.stderr.fileno(),
219 # mypids will hold the pids of all processes created.
223 # Using a log file requires that stdout and stderr
224 # are assigned to the process we're running.
225 if 1 not in fd_pipes or 2 not in fd_pipes:
226 raise ValueError(fd_pipes)
231 # Create a tee process, giving it our stdout and stderr
232 # as well as the read end of the pipe.
233 mypids.extend(spawn(('tee', '-i', '-a', logfile),
234 returnpid=True, fd_pipes={0:pr,
235 1:fd_pipes[1], 2:fd_pipes[2]}))
237 # We don't need the read end of the pipe, so close it.
240 # Assign the write end of the pipe to our stdout and stderr.
248 _exec(binary, mycommand, opt_name, fd_pipes,
249 env, gid, groups, uid, umask, pre_exec)
250 except Exception as e:
251 # We need to catch _any_ exception so that it doesn't
252 # propogate out of this function and cause exiting
253 # with anything other than os._exit()
254 sys.stderr.write("%s:\n %s\n" % (e, " ".join(mycommand)))
255 traceback.print_exc()
259 # Add the pid to our local and the global pid lists.
261 spawned_pids.append(pid)
263 # If we started a tee process the write side of the pipe is no
264 # longer needed, so close it.
268 # If the caller wants to handle cleaning up the processes, we tell
269 # it about all processes that were created.
273 # Otherwise we clean them up.
276 # Pull the last reader in the pipe chain. If all processes
277 # in the pipe are well behaved, it will die when the process
278 # it is reading from dies.
282 retval = os.waitpid(pid, 0)[1]
284 # When it's done, we can remove it from the
285 # global pid list as well.
286 spawned_pids.remove(pid)
289 # If it failed, kill off anything else that
292 if os.waitpid(pid, os.WNOHANG) == (0,0):
293 os.kill(pid, signal.SIGTERM)
295 spawned_pids.remove(pid)
297 # If it got a signal, return the signal that was sent.
299 return ((retval & 0xff) << 8)
301 # Otherwise, return its exit code.
304 # Everything succeeded
307 def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
311 Execute a given binary with options
313 @param binary: Name of program to execute
315 @param mycommand: Options for program
316 @type mycommand: String
317 @param opt_name: Name of process (defaults to binary)
318 @type opt_name: String
319 @param fd_pipes: Mapping pipes to destination; { 0:0, 1:1, 2:2 }
320 @type fd_pipes: Dictionary
321 @param env: Key,Value mapping for Environmental Variables
322 @type env: Dictionary
323 @param gid: Group ID to run the process under
325 @param groups: Groups the Process should be in.
326 @type groups: Integer
327 @param uid: User ID to run the process under
329 @param umask: an int representing a unix umask (see man chmod for umask details)
331 @param pre_exec: A function to be called with no arguments just prior to the exec call.
332 @type pre_exec: callable
334 @returns: Never returns (calls os.execve)
337 # If the process we're creating hasn't been given a name
338 # assign it the name of the executable.
340 opt_name = os.path.basename(binary)
342 # Set up the command's argument list.
344 myargs.extend(mycommand[1:])
346 # Set up the command's pipes.
348 # To protect from cases where direct assignment could
349 # clobber needed fds ({1:2, 2:1}) we first dupe the fds
352 my_fds[fd] = os.dup(fd_pipes[fd])
353 # Then assign them to what they should be.
355 os.dup2(my_fds[fd], fd)
356 # Then close _all_ fds that haven't been explictly
357 # requested to be kept open.
358 for fd in get_open_fds():
365 # Set requested process permissions.
377 # And switch to the new process.
378 os.execve(binary, myargs, env)
380 def find_binary(binary):
382 Given a binary name, find the binary in PATH
384 @param binary: Name of the binary to find
386 @rtype: None or string
387 @returns: full path to binary or None if the binary could not be located.
390 for path in os.getenv("PATH", "").split(":"):
391 filename = "%s/%s" % (path, binary)
392 if os.access(filename, os.X_OK) and os.path.isfile(filename):