/[gentoo-src]/portage/pym/portage_exec.py
Gentoo

Contents of /portage/pym/portage_exec.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.21 - (show annotations) (download) (as text)
Sat Feb 26 06:41:48 2005 UTC (9 years, 9 months ago) by ferringb
Branch: MAIN
CVS Tags: HEAD
Branch point for: portage_2_1
Changes since 1.20: +3 -3 lines
File MIME type: text/x-python
merged forward porting of portage_exec changes that jason split off (unknown hunk).
Nothing major, mainly a logic conversion that's now one func/stat call, rather then 2.

1 # portage.py -- core Portage functionality
2 # Copyright 1998-2004 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4 # $Header: /var/cvsroot/gentoo-src/portage/pym/portage_exec.py,v 1.20 2005/02/26 06:35:20 jstubbs Exp $
5 cvs_id_string="$Id: portage_exec.py,v 1.20 2005/02/26 06:35:20 jstubbs Exp $"[5:-2]
6
7 import os,types,string,sys
8 import signal
9 import portage_data
10 import portage_util
11 import portage_const
12
13 selinux_capable = False
14 sandbox_capable = os.path.exists(portage_const.SANDBOX_BINARY)
15 userpriv_capable = (os.getuid() == 0)
16 fakeroot_capable = False
17
18
19 try:
20 import resource
21 max_fd_limit=resource.getrlimit(RLIMIT_NOFILE)
22 except SystemExit, e:
23 raise
24 except:
25 # hokay, no resource module.
26 max_fd_limit=256
27
28 # pids this process knows of.
29 spawned_pids = []
30
31 def cleanup(pids,block_exceptions=True):
32 """function to go through and reap the list of pids passed to it"""
33 global spawned_pids
34 if type(pids) == int:
35 pids = [pids]
36 for x in pids:
37 try:
38 os.kill(x,signal.SIGTERM)
39 if os.waitpid(x,os.WNOHANG)[1] == 0:
40 # feisty bugger, still alive.
41 os.kill(x,signal.SIGKILL)
42 os.waitpid(x,0)
43
44 except OSError, oe:
45 if block_exceptions:
46 pass
47 if oe.errno not in (10,3):
48 raise oe
49 except SystemExit:
50 raise
51 except Exception:
52 if block_exceptions:
53 pass
54 try: spawned_pids.remove(x)
55 except IndexError: pass
56
57 from portage_const import BASH_BINARY,SANDBOX_BINARY,SANDBOX_PIDS_FILE
58
59 def spawn_bash(mycommand,env={},debug=False,opt_name=None,**keywords):
60 """spawn mycommand as an arguement to bash"""
61 args=[BASH_BINARY]
62 if not opt_name:
63 opt_name=mycommand.split()[0]
64 if not env.has_key("BASH_ENV"):
65 env["BASH_ENV"] = "/etc/spork/is/not/valid/profile.env"
66 if debug:
67 args.append("-x")
68 args.append("-c")
69 args.append(mycommand)
70 return spawn(args,env=env,opt_name=opt_name,**keywords)
71
72 def spawn_sandbox(mycommand,uid=None,opt_name=None,**keywords):
73 """spawn mycommand as an arguement to the sandbox binary.
74 basically, LD_PRELOAD="libsandbox.so" mycommand"""
75 args=[SANDBOX_BINARY]
76 if not opt_name:
77 opt_name=mycommand.split()[0]
78 args.append(mycommand)
79 if not uid:
80 uid=os.getuid()
81 try:
82 os.chown(SANDBOX_PIDS_FILE,uid,portage_data.portage_gid)
83 os.chmod(SANDBOX_PIDS_FILE,0664)
84 except SystemExit, e:
85 raise
86 except:
87 pass
88 return spawn(args,uid=uid,opt_name=opt_name,**keywords)
89
90 def find_binary(myc):
91 """look through the environmental path for an executable file named whatever myc is"""
92 # this sucks. badly.
93 p=os.getenv("PATH")
94 if p == None:
95 return None
96 for x in p.split(":"):
97 #if it exists, and is executable
98 if os.path.exists("%s/%s" % (x,myc)) and os.stat("%s/%s" % (x,myc))[0] & 0x0248:
99 return "%s/%s" % (x,myc)
100 return None
101
102
103 def spawn_func(func,args=[],kwargs={},exit_func=None,**keywords):
104 """spawn a python function in a fork
105 func: python function
106 args: positional args to positionally expand for the function
107 kwargs: optional args to expand for the function call
108 exit_func: optional function for parsing the return of func, and
109 returning the desired exit code
110 note exit_func should handle applicable exceptions. Unhandled exemptions are caught,
111 and the process exits with a code of 1."""
112 if exit_func:
113 return spawn((func,args,kwargs,exit_func), func_call=True,**keywords)
114 return spawn((func,args,kwargs), func_call=True,**keywords)
115
116 # base spawn function
117 def spawn(mycommand,env={},raw_exit_code=False,opt_name=None,fd_pipes=None,returnpid=False,\
118 uid=None,gid=None,groups=None,umask=None,logfile=None,path_lookup=True, selinux_context=None,
119 raise_signals=False, func_call=False):
120 """base fork/execve function.
121 mycommand is the desired command- if you need a command to execute in a bash/sandbox/fakeroot
122 environment, use the appropriate spawn call. This is a straight fork/exec code path.
123 Can either have a tuple, or a string passed in. If uid/gid/groups/umask specified, it changes
124 the forked process to said value. If path_lookup is on, a non-absolute command will be converted
125 to an absolute command, otherwise it returns None.
126
127 selinux_context is the desired context, dependant on selinux being available.
128 opt_name controls the name the processor goes by.
129 fd_pipes controls which file descriptor numbers are left open in the forked process- it's a dict of
130 current fd's raw fd #, desired #.
131
132 func_call is a boolean for specifying to execute a python function- use spawn_func instead.
133 raise_signals is questionable. Basically throw an exception if signal'd. No exception is thrown
134 if raw_input is on.
135
136 logfile overloads the specified fd's to write to a tee process which logs to logfile
137 returnpid returns the relevant pids (a list, including the logging process if logfile is on).
138
139 non-returnpid calls to spawn will block till the process has exited, returning the exitcode/signal
140 raw_exit_code controls whether the actual waitpid result is returned, or intrepretted."""
141 myc=''
142 if not func_call:
143 if type(mycommand)==types.StringType:
144 mycommand=mycommand.split()
145 myc = mycommand[0]
146 if not os.access(myc, os.X_OK):
147 if not path_lookup:
148 return None
149 myc = find_binary(myc)
150 if myc == None:
151 return None
152 mypid=[]
153 if logfile:
154 pr,pw=os.pipe()
155 mypid.extend(spawn(('tee','-i','-a',logfile),returnpid=True,fd_pipes={0:pr,1:1,2:2}))
156 retval=os.waitpid(mypid[-1],os.WNOHANG)[1]
157 if retval != 0:
158 # he's dead jim.
159 if raw_exit_code:
160 return retval
161 return process_exit_code(retval)
162
163 if fd_pipes == None:
164 fd_pipes={}
165 fd_pipes[0] = 0
166 fd_pipes[1]=pw
167 fd_pipes[2]=pw
168
169 if not opt_name:
170 opt_name = mycommand[0]
171 myargs=[opt_name]
172 myargs.extend(mycommand[1:])
173 global spawned_pids
174 mypid.append(os.fork())
175 if mypid[-1] != 0:
176 #log the bugger.
177 spawned_pids.extend(mypid)
178
179 if mypid[-1] == 0:
180 if func_call:
181 spawned_pids = []
182
183 # this may look ugly, but basically it moves file descriptors around to ensure no
184 # handles that are needed are accidentally closed during the final dup2 calls.
185 trg_fd=[]
186 if type(fd_pipes)==types.DictType:
187 src_fd=[]
188 k=fd_pipes.keys()
189 k.sort()
190
191 #build list of which fds will be where, and where they are at currently
192 for x in k:
193 trg_fd.append(x)
194 src_fd.append(fd_pipes[x])
195
196 # run through said list dup'ing descriptors so that they won't be waxed
197 # by other dup calls.
198 for x in range(0,len(trg_fd)):
199 if trg_fd[x] == src_fd[x]:
200 continue
201 if trg_fd[x] in src_fd[x+1:]:
202 new=os.dup2(trg_fd[x],max(src_fd) + 1)
203 os.close(trg_fd[x])
204 try:
205 while True:
206 src_fd[s.index(trg_fd[x])]=new
207 except SystemExit, e:
208 raise
209 except:
210 pass
211
212 # transfer the fds to their final pre-exec position.
213 for x in range(0,len(trg_fd)):
214 if trg_fd[x] != src_fd[x]:
215 os.dup2(src_fd[x], trg_fd[x])
216 else:
217 trg_fd=[0,1,2]
218
219 # wax all open descriptors that weren't requested be left open.
220 for x in range(0,max_fd_limit):
221 if x not in trg_fd:
222 try:
223 os.close(x)
224 except SystemExit, e:
225 raise
226 except:
227 pass
228
229 # note this order must be preserved- can't change gid/groups if you change uid first.
230 if selinux_capable and selinux_context:
231 import selinux
232 selinux.setexec(selinux_context)
233 if gid:
234 os.setgid(gid)
235 if groups:
236 os.setgroups(groups)
237 if uid:
238 os.setuid(uid)
239 if umask:
240 os.umask(umask)
241
242 try:
243 #print "execing", myc, myargs
244 if func_call:
245 # either use a passed in func for interpretting the results, or return if no exception.
246 # note the passed in list, and dict are expanded.
247 if len(mycommand) == 4:
248 os._exit(mycommand[3](mycommand[0](*mycommand[1],**mycommand[2])))
249 try:
250 mycommand[0](*mycommand[1],**mycommand[2])
251 except Exception,e:
252 print "caught exception",e," in forked func",mycommand[0]
253 sys.exit(0)
254
255 os.execve(myc,myargs,env)
256 except SystemExit, e:
257 raise
258 except Exception, e:
259 if not func_call:
260 raise str(e)+":\n "+myc+" "+string.join(myargs)
261 print "func call failed"
262
263 # If the execve fails, we need to report it, and exit
264 # *carefully* --- report error here
265 os._exit(1)
266 sys.exit(1)
267 return # should never get reached
268
269 # if we were logging, kill the pipes.
270 if logfile:
271 os.close(pr)
272 os.close(pw)
273
274 if returnpid:
275 return mypid
276
277 # loop through pids (typically one, unless logging), either waiting on their death, or waxing them
278 # if the main pid (mycommand) returned badly.
279 while len(mypid):
280 retval=os.waitpid(mypid[-1],0)[1]
281 if retval != 0:
282 cleanup(mypid[0:-1],block_exceptions=False)
283 # at this point we've killed all other kid pids generated via this call.
284 # return now.
285 if raw_exit_code:
286 return retval
287 return process_exit_code(retval,throw_signals=raise_signals)
288 else:
289 mypid.pop(-1)
290 cleanup(mypid)
291 return 0
292
293 def process_exit_code(retval,throw_signals=False):
294 """process a waitpid returned exit code, returning exit code if it exit'd, or the
295 signal if it died from signalling
296 if throw_signals is on, it raises a SystemExit if the process was signaled.
297 This is intended for usage with threads, although at the moment you can't signal individual
298 threads in python, only the master thread, so it's a questionable option."""
299 if (retval & 0xff)==0:
300 return retval >> 8 # return exit code
301 else:
302 if throw_signals:
303 #use systemexit, since portage is stupid about exception catching.
304 raise SystemExit()
305 return (retval & 0xff) << 8 # interrupted by signal
306
307 def spawn_get_output(mycommand,spawn_type=spawn,raw_exit_code=False,emulate_gso=True, \
308 collect_fds=[1],fd_pipes=None,**keywords):
309 """call spawn, collecting the output to fd's specified in collect_fds list
310 emulate_gso is a compatability hack to emulate commands.getstatusoutput's return, minus the
311 requirement it always be a bash call (spawn_type controls the actual spawn call), and minus the
312 'lets let log only stdin and let stderr slide by'.
313
314 emulate_gso was deprecated from the day it was added, so convert your code over.
315 spawn_type is the passed in function to call- typically spawn_bash, spawn, spawn_sandbox, or spawn_fakeroot"""
316 global selinux_capable
317 pr,pw=os.pipe()
318
319 if type(spawn_type) not in [types.FunctionType, types.MethodType]:
320 s="spawn_type must be passed a function, not",type(spawn_type),spawn_type
321 raise Exception,s
322
323 if fd_pipes==None:
324 fd_pipes={}
325 fd_pipes[0] = 0
326
327 for x in collect_fds:
328 fd_pipes[x] = pw
329 keywords["returnpid"]=True
330
331 mypid=spawn_type(mycommand,fd_pipes=fd_pipes,**keywords)
332 os.close(pw)
333 if type(mypid) != types.ListType:
334 os.close(pr)
335 return [mypid, "%s: No such file or directory" % mycommand.split()[0]]
336
337 fd=os.fdopen(pr,"r")
338 mydata=fd.readlines()
339 fd.close()
340 if emulate_gso:
341 mydata=string.join(mydata)
342 if len(mydata) and mydata[-1] == "\n":
343 mydata=mydata[:-1]
344 retval=os.waitpid(mypid[0],0)[1]
345 cleanup(mypid)
346 if raw_exit_code:
347 return [retval,mydata]
348 retval=process_exit_code(retval)
349 return [retval, mydata]
350
351 def spawn_fakeroot(mycommand, save_file, env={}, opt_name=None,**keywords):
352 """spawn a fakerooted process, saving/loading from save_file"""
353 if opt_name == None:
354 opt_name = "fakeroot %s" % mycommand
355 myc=[portage_const.FAKEROOT_PATH,"-u","-b","20","-s","%s" % save_file]
356 if not save_file:
357 import traceback
358 traceback.print_stack()
359 print "save_file=",save_file
360 print "this is invalid"
361 sys.exit(1)
362
363 if os.path.exists(save_file):
364 myc.extend(["-i","%s" % save_file])
365 myc.extend(["--"])
366 mycl = mycommand.split()
367 if not os.path.exists(mycl[0]):
368 mycl[0] = find_binary(mycl[0])
369 if mycl[0] == None:
370 return None
371 myc.extend(mycl)
372 return spawn(myc,env=env,opt_name=opt_name,**keywords)
373
374 if os.path.exists(portage_const.FAKEROOT_PATH):
375 r,s=spawn_get_output((portage_const.FAKEROOT_PATH, "--version"),emulate_gso=False,
376 fd_pipes={1:1,2:2})
377 if r == 0:
378 if len(s) == 1 and "version 1." in s[0]:
379 fakeroot_capable = True
380 else:
381 print "fakeroot not available, need at least version 1.0"
382

  ViewVC Help
Powered by ViewVC 1.1.20