Gentoo Archives: gentoo-catalyst

From: Brian Dolbec <dolsen@g.o>
To: gentoo-catalyst@l.g.o
Subject: [gentoo-catalyst] [PATCH 5/5] Remove redundant /bin/bash additions in cmd() calls
Date: Thu, 11 Sep 2014 03:44:00
Message-Id: 1410407024-2445-6-git-send-email-dolsen@gentoo.org
In Reply to: [gentoo-catalyst] [PATCH 0/5] Next group of 5 patches by Brian Dolbec
1 Remove old spawn_* functions copied from portage.
2 cmd() now uses subprocess.Popen().
3 ---
4 catalyst/support.py | 301 +++----------------------------
5 catalyst/targets/generic_stage_target.py | 26 +--
6 catalyst/targets/grp_target.py | 2 +-
7 catalyst/targets/netboot2_target.py | 4 +-
8 catalyst/targets/netboot_target.py | 8 +-
9 catalyst/targets/tinderbox_target.py | 2 +-
10 6 files changed, 42 insertions(+), 301 deletions(-)
11
12 diff --git a/catalyst/support.py b/catalyst/support.py
13 index e2d64a1..aaacaa9 100644
14 --- a/catalyst/support.py
15 +++ b/catalyst/support.py
16 @@ -4,9 +4,10 @@ import string
17 import os
18 import types
19 import re
20 -import signal
21 import traceback
22 import time
23 +from subprocess import Popen
24 +
25
26 from catalyst.defaults import verbosity, valid_config_file_values
27
28 @@ -31,34 +32,6 @@ except:
29 spawned_pids = []
30
31
32 -def cleanup(pids,block_exceptions=True):
33 - """function to go through and reap the list of pids passed to it"""
34 - global spawned_pids
35 - if type(pids) == int:
36 - pids = [pids]
37 - for x in pids:
38 - try:
39 - os.kill(x,signal.SIGTERM)
40 - if os.waitpid(x,os.WNOHANG)[1] == 0:
41 - # feisty bugger, still alive.
42 - os.kill(x,signal.SIGKILL)
43 - os.waitpid(x,0)
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:
55 - spawned_pids.remove(x)
56 - except IndexError:
57 - pass
58 -
59 -
60 # a function to turn a string of non-printable characters
61 # into a string of hex characters
62 def hexify(str):
63 @@ -79,8 +52,8 @@ def read_from_clst(file):
64 return -1
65 #raise CatalystError, "Could not open file "+file
66 for line in myf.readlines():
67 - #line = string.replace(line, "\n", "") # drop newline
68 - myline = myline + line
69 + #line = string.replace(line, "\n", "") # drop newline
70 + myline = myline + line
71 myf.close()
72 return myline
73
74 @@ -145,259 +118,27 @@ def find_binary(myc):
75 return None
76
77
78 -def spawn_bash(mycommand,env={},debug=False,opt_name=None,**keywords):
79 - """spawn mycommand as an arguement to bash"""
80 - args=[BASH_BINARY]
81 - if not opt_name:
82 - opt_name=mycommand.split()[0]
83 - if "BASH_ENV" not in env:
84 - env["BASH_ENV"] = "/etc/spork/is/not/valid/profile.env"
85 - if debug:
86 - args.append("-x")
87 - args.append("-c")
88 - args.append(mycommand)
89 - return spawn(args,env=env,opt_name=opt_name,**keywords)
90 -
91 -
92 -def spawn_get_output(mycommand,raw_exit_code=False,emulate_gso=True, \
93 - collect_fds=[1],fd_pipes=None,**keywords):
94 - """call spawn, collecting the output to fd's specified in collect_fds list
95 - emulate_gso is a compatability hack to emulate commands.getstatusoutput's return, minus the
96 - requirement it always be a bash call (spawn_type controls the actual spawn call), and minus the
97 - 'lets let log only stdin and let stderr slide by'.
98 -
99 - emulate_gso was deprecated from the day it was added, so convert your code over.
100 - spawn_type is the passed in function to call- typically spawn_bash, spawn, spawn_sandbox, or spawn_fakeroot"""
101 - global selinux_capable
102 - pr,pw=os.pipe()
103 -
104 - if fd_pipes==None:
105 - fd_pipes={}
106 - fd_pipes[0] = 0
107 -
108 - for x in collect_fds:
109 - fd_pipes[x] = pw
110 - keywords["returnpid"]=True
111 -
112 - mypid=spawn_bash(mycommand,fd_pipes=fd_pipes,**keywords)
113 - os.close(pw)
114 - if type(mypid) != types.ListType:
115 - os.close(pr)
116 - return [mypid, "%s: No such file or directory" % mycommand.split()[0]]
117 -
118 - fd=os.fdopen(pr,"r")
119 - mydata=fd.readlines()
120 - fd.close()
121 - if emulate_gso:
122 - mydata=string.join(mydata)
123 - if len(mydata) and mydata[-1] == "\n":
124 - mydata=mydata[:-1]
125 - retval=os.waitpid(mypid[0],0)[1]
126 - cleanup(mypid)
127 - if raw_exit_code:
128 - return [retval,mydata]
129 - retval=process_exit_code(retval)
130 - return [retval, mydata]
131 -
132 -
133 -# base spawn function
134 -def spawn(mycommand,env={},raw_exit_code=False,opt_name=None,fd_pipes=None,returnpid=False,\
135 - uid=None,gid=None,groups=None,umask=None,logfile=None,path_lookup=True,\
136 - selinux_context=None, raise_signals=False, func_call=False):
137 - """base fork/execve function.
138 - mycommand is the desired command- if you need a command to execute in a bash/sandbox/fakeroot
139 - environment, use the appropriate spawn call. This is a straight fork/exec code path.
140 - Can either have a tuple, or a string passed in. If uid/gid/groups/umask specified, it changes
141 - the forked process to said value. If path_lookup is on, a non-absolute command will be converted
142 - to an absolute command, otherwise it returns None.
143 -
144 - selinux_context is the desired context, dependant on selinux being available.
145 - opt_name controls the name the processor goes by.
146 - fd_pipes controls which file descriptor numbers are left open in the forked process- it's a dict of
147 - current fd's raw fd #, desired #.
148 -
149 - func_call is a boolean for specifying to execute a python function- use spawn_func instead.
150 - raise_signals is questionable. Basically throw an exception if signal'd. No exception is thrown
151 - if raw_input is on.
152 -
153 - logfile overloads the specified fd's to write to a tee process which logs to logfile
154 - returnpid returns the relevant pids (a list, including the logging process if logfile is on).
155 -
156 - non-returnpid calls to spawn will block till the process has exited, returning the exitcode/signal
157 - raw_exit_code controls whether the actual waitpid result is returned, or intrepretted."""
158 -
159 - myc=''
160 - if not func_call:
161 - if type(mycommand)==types.StringType:
162 - mycommand=mycommand.split()
163 - myc = mycommand[0]
164 - if not os.access(myc, os.X_OK):
165 - if not path_lookup:
166 - return None
167 - myc = find_binary(myc)
168 - if myc == None:
169 - return None
170 - mypid=[]
171 - if logfile:
172 - pr,pw=os.pipe()
173 - mypid.extend(spawn(('tee','-i','-a',logfile),returnpid=True,fd_pipes={0:pr,1:1,2:2}))
174 - retval=os.waitpid(mypid[-1],os.WNOHANG)[1]
175 - if retval != 0:
176 - # he's dead jim.
177 - if raw_exit_code:
178 - return retval
179 - return process_exit_code(retval)
180 -
181 - if fd_pipes == None:
182 - fd_pipes={}
183 - fd_pipes[0] = 0
184 - fd_pipes[1]=pw
185 - fd_pipes[2]=pw
186 -
187 - if not opt_name:
188 - opt_name = mycommand[0]
189 - myargs=[opt_name]
190 - myargs.extend(mycommand[1:])
191 - global spawned_pids
192 - mypid.append(os.fork())
193 - if mypid[-1] != 0:
194 - #log the bugger.
195 - spawned_pids.extend(mypid)
196 -
197 - if mypid[-1] == 0:
198 - if func_call:
199 - spawned_pids = []
200 -
201 - # this may look ugly, but basically it moves file descriptors around to ensure no
202 - # handles that are needed are accidentally closed during the final dup2 calls.
203 - trg_fd=[]
204 - if type(fd_pipes)==types.DictType:
205 - src_fd=[]
206 - k=fd_pipes.keys()
207 - k.sort()
208 -
209 - #build list of which fds will be where, and where they are at currently
210 - for x in k:
211 - trg_fd.append(x)
212 - src_fd.append(fd_pipes[x])
213 -
214 - # run through said list dup'ing descriptors so that they won't be waxed
215 - # by other dup calls.
216 - for x in range(0,len(trg_fd)):
217 - if trg_fd[x] == src_fd[x]:
218 - continue
219 - if trg_fd[x] in src_fd[x+1:]:
220 - os.close(trg_fd[x])
221 -
222 - # transfer the fds to their final pre-exec position.
223 - for x in range(0,len(trg_fd)):
224 - if trg_fd[x] != src_fd[x]:
225 - os.dup2(src_fd[x], trg_fd[x])
226 - else:
227 - trg_fd=[0,1,2]
228 -
229 - # wax all open descriptors that weren't requested be left open.
230 - for x in range(0,max_fd_limit):
231 - if x not in trg_fd:
232 - try:
233 - os.close(x)
234 - except SystemExit, e:
235 - raise
236 - except:
237 - pass
238 -
239 - # note this order must be preserved- can't change gid/groups if you change uid first.
240 - if selinux_capable and selinux_context:
241 - import selinux
242 - selinux.setexec(selinux_context)
243 - if gid:
244 - os.setgid(gid)
245 - if groups:
246 - os.setgroups(groups)
247 - if uid:
248 - os.setuid(uid)
249 - if umask:
250 - os.umask(umask)
251 - else:
252 - os.umask(022)
253 -
254 - try:
255 - #print "execing", myc, myargs
256 - if func_call:
257 - # either use a passed in func for interpretting the results, or return if no exception.
258 - # note the passed in list, and dict are expanded.
259 - if len(mycommand) == 4:
260 - os._exit(mycommand[3](mycommand[0](*mycommand[1],**mycommand[2])))
261 - try:
262 - mycommand[0](*mycommand[1],**mycommand[2])
263 - except Exception,e:
264 - print "caught exception",e," in forked func",mycommand[0]
265 - sys.exit(0)
266 -
267 - os.execve(myc,myargs,env)
268 - except SystemExit, e:
269 - raise
270 - except Exception, e:
271 - if not func_call:
272 - raise str(e)+":\n "+myc+" "+string.join(myargs)
273 - print "func call failed"
274 -
275 - # If the execve fails, we need to report it, and exit
276 - # *carefully* --- report error here
277 - os._exit(1)
278 - sys.exit(1)
279 - return # should never get reached
280 -
281 - # if we were logging, kill the pipes.
282 - if logfile:
283 - os.close(pr)
284 - os.close(pw)
285 -
286 - if returnpid:
287 - return mypid
288 -
289 - # loop through pids (typically one, unless logging), either waiting on their death, or waxing them
290 - # if the main pid (mycommand) returned badly.
291 - while len(mypid):
292 - retval=os.waitpid(mypid[-1],0)[1]
293 - if retval != 0:
294 - cleanup(mypid[0:-1],block_exceptions=False)
295 - # at this point we've killed all other kid pids generated via this call.
296 - # return now.
297 - if raw_exit_code:
298 - return retval
299 - return process_exit_code(retval,throw_signals=raise_signals)
300 - else:
301 - mypid.pop(-1)
302 - cleanup(mypid)
303 - return 0
304 -
305 -
306 -def cmd(mycmd,myexc="",env={}):
307 +def cmd(mycmd, myexc="", env={}, debug=False):
308 try:
309 sys.stdout.flush()
310 - retval=spawn_bash(mycmd,env)
311 - if retval != 0:
312 - raise CatalystError,myexc
313 + args=[BASH_BINARY]
314 + if "BASH_ENV" not in env:
315 + env["BASH_ENV"] = "/etc/spork/is/not/valid/profile.env"
316 + if debug:
317 + args.append("-x")
318 + args.append("-c")
319 + args.append(mycmd)
320 +
321 + if debug:
322 + print "cmd(); args =", args
323 + proc = Popen(args, env=env)
324 + if proc.wait() != 0:
325 + raise CatalystError("cmd() NON-zero return value from: %s" % myexc,
326 + print_traceback=False)
327 except:
328 raise
329
330
331 -def process_exit_code(retval,throw_signals=False):
332 - """process a waitpid returned exit code, returning exit code if it exit'd, or the
333 - signal if it died from signalling
334 - if throw_signals is on, it raises a SystemExit if the process was signaled.
335 - This is intended for usage with threads, although at the moment you can't signal individual
336 - threads in python, only the master thread, so it's a questionable option."""
337 - if (retval & 0xff)==0:
338 - return retval >> 8 # return exit code
339 - else:
340 - if throw_signals:
341 - #use systemexit, since portage is stupid about exception catching.
342 - raise SystemExit()
343 - return (retval & 0xff) << 8 # interrupted by signal
344 -
345 -
346 def file_locate(settings,filelist,expand=1):
347 #if expand=1, non-absolute paths will be accepted and
348 # expanded to os.getcwd()+"/"+localpath if file exists
349 @@ -459,8 +200,8 @@ def parse_makeconf(mylines):
350 mobj=pat.match(myline)
351 pos += 1
352 if mobj.group(2):
353 - clean_string = re.sub(r"\"",r"",mobj.group(2))
354 - mymakeconf[mobj.group(1)]=clean_string
355 + clean_string = re.sub(r"\"",r"",mobj.group(2))
356 + mymakeconf[mobj.group(1)]=clean_string
357 return mymakeconf
358
359
360 diff --git a/catalyst/targets/generic_stage_target.py b/catalyst/targets/generic_stage_target.py
361 index c6b8dcc..296eee3 100644
362 --- a/catalyst/targets/generic_stage_target.py
363 +++ b/catalyst/targets/generic_stage_target.py
364 @@ -1136,7 +1136,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
365 else:
366 if "fsscript" in self.settings:
367 if os.path.exists(self.settings["controller_file"]):
368 - cmd("/bin/bash "+self.settings["controller_file"]+\
369 + cmd(self.settings["controller_file"]+\
370 " fsscript","fsscript script failed.",env=self.env)
371 touch(fsscript_resume)
372
373 @@ -1147,7 +1147,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
374 print "Resume point detected, skipping rcupdate operation..."
375 else:
376 if os.path.exists(self.settings["controller_file"]):
377 - cmd("/bin/bash "+self.settings["controller_file"]+" rc-update",\
378 + cmd(self.settings["controller_file"]+" rc-update",\
379 "rc-update script failed.",env=self.env)
380 touch(rcupdate_resume)
381
382 @@ -1183,7 +1183,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
383 "Could not remove stray files in /etc",env=self.env)
384
385 if os.path.exists(self.settings["controller_file"]):
386 - cmd("/bin/bash "+self.settings["controller_file"]+" clean",\
387 + cmd(self.settings["controller_file"]+" clean",\
388 "clean script failed.",env=self.env)
389 touch(clean_resume)
390
391 @@ -1232,7 +1232,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
392 os.system("rm -rf "+self.settings["chroot_path"]+x)
393 try:
394 if os.path.exists(self.settings["controller_file"]):
395 - cmd("/bin/bash "+self.settings["controller_file"]+\
396 + cmd(self.settings["controller_file"]+\
397 " clean","Clean failed.",env=self.env)
398 touch(remove_resume)
399 except:
400 @@ -1247,7 +1247,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
401 else:
402 try:
403 if os.path.exists(self.settings["controller_file"]):
404 - cmd("/bin/bash "+self.settings["controller_file"]+\
405 + cmd(self.settings["controller_file"]+\
406 " preclean","preclean script failed.",env=self.env)
407 touch(preclean_resume)
408
409 @@ -1289,7 +1289,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
410 else:
411 try:
412 if os.path.exists(self.settings["controller_file"]):
413 - cmd("/bin/bash "+self.settings["controller_file"]+" run",\
414 + cmd(self.settings["controller_file"]+" run",\
415 "run script failed.",env=self.env)
416 touch(run_local_resume)
417
418 @@ -1388,7 +1388,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
419
420 """ Before cleaning, unmerge stuff """
421 try:
422 - cmd("/bin/bash "+self.settings["controller_file"]+\
423 + cmd(self.settings["controller_file"]+\
424 " unmerge "+ myunmerge,"Unmerge script failed.",\
425 env=self.env)
426 print "unmerge shell script"
427 @@ -1405,7 +1405,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
428 print "Resume point detected, skipping target_setup operation..."
429 else:
430 print "Setting up filesystems per filesystem type"
431 - cmd("/bin/bash "+self.settings["controller_file"]+\
432 + cmd(self.settings["controller_file"]+\
433 " target_image_setup "+ self.settings["target_path"],\
434 "target_image_setup script failed.",env=self.env)
435 touch(target_setup_resume)
436 @@ -1434,7 +1434,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
437 else:
438 """ Create the ISO """
439 if "iso" in self.settings:
440 - cmd("/bin/bash "+self.settings["controller_file"]+" iso "+\
441 + cmd(self.settings["controller_file"]+" iso "+\
442 self.settings["iso"],"ISO creation script failed.",\
443 env=self.env)
444 self.gen_contents_file(self.settings["iso"])
445 @@ -1461,7 +1461,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
446 list_bashify(self.settings[self.settings["spec_prefix"]\
447 +"/packages"])
448 try:
449 - cmd("/bin/bash "+self.settings["controller_file"]+\
450 + cmd(self.settings["controller_file"]+\
451 " build_packages "+mypack,\
452 "Error in attempt to build packages",env=self.env)
453 touch(build_packages_resume)
454 @@ -1486,7 +1486,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
455 """
456 Execute the script that sets up the kernel build environment
457 """
458 - cmd("/bin/bash "+self.settings["controller_file"]+\
459 + cmd(self.settings["controller_file"]+\
460 " pre-kmerge ","Runscript pre-kmerge failed",\
461 env=self.env)
462 for kname in mynames:
463 @@ -1603,7 +1603,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
464 print "Resume point detected, skipping bootloader operation..."
465 else:
466 try:
467 - cmd("/bin/bash "+self.settings["controller_file"]+\
468 + cmd(self.settings["controller_file"]+\
469 " bootloader " + self.settings["target_path"],\
470 "Bootloader script failed.",env=self.env)
471 touch(bootloader_resume)
472 @@ -1619,7 +1619,7 @@ class generic_stage_target(TargetBase, ClearBase, GenBase):
473 print "Resume point detected, skipping build_packages operation..."
474 else:
475 try:
476 - cmd("/bin/bash "+self.settings["controller_file"]+\
477 + cmd(self.settings["controller_file"]+\
478 " livecd-update","livecd-update failed.",env=self.env)
479 touch(livecd_update_resume)
480
481 diff --git a/catalyst/targets/grp_target.py b/catalyst/targets/grp_target.py
482 index a8309a8..033db75 100644
483 --- a/catalyst/targets/grp_target.py
484 +++ b/catalyst/targets/grp_target.py
485 @@ -54,7 +54,7 @@ class grp_target(generic_stage_target):
486 # example call: "grp.sh run pkgset cd1 xmms vim sys-apps/gleep"
487 mypackages=list_bashify(self.settings["grp/"+pkgset+"/packages"])
488 try:
489 - cmd("/bin/bash "+self.settings["controller_file"]+" run "+self.settings["grp/"+pkgset+"/type"]\
490 + cmd(self.settings["controller_file"]+" run "+self.settings["grp/"+pkgset+"/type"]\
491 +" "+pkgset+" "+mypackages,env=self.env)
492
493 except CatalystError:
494 diff --git a/catalyst/targets/netboot2_target.py b/catalyst/targets/netboot2_target.py
495 index 8809dd0..ea07d76 100644
496 --- a/catalyst/targets/netboot2_target.py
497 +++ b/catalyst/targets/netboot2_target.py
498 @@ -87,7 +87,7 @@ class netboot2_target(generic_stage_target):
499 myfiles.append(self.settings["netboot2/extra_files"])
500
501 try:
502 - cmd("/bin/bash "+self.settings["controller_file"]+\
503 + cmd(self.settings["controller_file"]+\
504 " image " + list_bashify(myfiles),env=self.env)
505 except CatalystError:
506 self.unbind()
507 @@ -112,7 +112,7 @@ class netboot2_target(generic_stage_target):
508 # no auto resume here as we always want the
509 # freshest images moved
510 try:
511 - cmd("/bin/bash "+self.settings["controller_file"]+\
512 + cmd(self.settings["controller_file"]+\
513 " final",env=self.env)
514 print ">>> Netboot Build Finished!"
515 except CatalystError:
516 diff --git a/catalyst/targets/netboot_target.py b/catalyst/targets/netboot_target.py
517 index 9d01b7e..ae1eb04 100644
518 --- a/catalyst/targets/netboot_target.py
519 +++ b/catalyst/targets/netboot_target.py
520 @@ -59,7 +59,7 @@ class netboot_target(generic_stage_target):
521 # if "netboot/packages" in self.settings:
522 # mypack=list_bashify(self.settings["netboot/packages"])
523 # try:
524 -# cmd("/bin/bash "+self.settings["controller_file"]+" packages "+mypack,env=self.env)
525 +# cmd(self.settings["controller_file"]+" packages "+mypack,env=self.env)
526 # except CatalystError:
527 # self.unbind()
528 # raise CatalystError,"netboot build aborting due to error."
529 @@ -71,7 +71,7 @@ class netboot_target(generic_stage_target):
530 else:
531 mycmd = ""
532 try:
533 - cmd("/bin/bash "+self.settings["controller_file"]+" busybox "+ mycmd,env=self.env)
534 + cmd(self.settings["controller_file"]+" busybox "+ mycmd,env=self.env)
535 except CatalystError:
536 self.unbind()
537 raise CatalystError,"netboot build aborting due to error."
538 @@ -99,7 +99,7 @@ class netboot_target(generic_stage_target):
539 myfiles.append(self.settings["netboot/extra_files"])
540
541 try:
542 - cmd("/bin/bash "+self.settings["controller_file"]+\
543 + cmd(self.settings["controller_file"]+\
544 " image " + list_bashify(myfiles),env=self.env)
545 except CatalystError:
546 self.unbind()
547 @@ -108,7 +108,7 @@ class netboot_target(generic_stage_target):
548 def create_netboot_files(self):
549 # finish it all up
550 try:
551 - cmd("/bin/bash "+self.settings["controller_file"]+" finish",env=self.env)
552 + cmd(self.settings["controller_file"]+" finish",env=self.env)
553 except CatalystError:
554 self.unbind()
555 raise CatalystError,"netboot build aborting due to error."
556 diff --git a/catalyst/targets/tinderbox_target.py b/catalyst/targets/tinderbox_target.py
557 index 1d31989..ea11d3f 100644
558 --- a/catalyst/targets/tinderbox_target.py
559 +++ b/catalyst/targets/tinderbox_target.py
560 @@ -21,7 +21,7 @@ class tinderbox_target(generic_stage_target):
561 # example call: "grp.sh run xmms vim sys-apps/gleep"
562 try:
563 if os.path.exists(self.settings["controller_file"]):
564 - cmd("/bin/bash "+self.settings["controller_file"]+" run "+\
565 + cmd(self.settings["controller_file"]+" run "+\
566 list_bashify(self.settings["tinderbox/packages"]),"run script failed.",env=self.env)
567
568 except CatalystError:
569 --
570 2.1.0