1 |
--- |
2 |
catalyst/support.py | 405 +++++++++++++++++++++++++++------------------------- |
3 |
1 file changed, 213 insertions(+), 192 deletions(-) |
4 |
|
5 |
diff --git a/catalyst/support.py b/catalyst/support.py |
6 |
index feaa645..e2d64a1 100644 |
7 |
--- a/catalyst/support.py |
8 |
+++ b/catalyst/support.py |
9 |
@@ -1,12 +1,19 @@ |
10 |
|
11 |
-import sys,string,os,types,re,signal,traceback,time |
12 |
-#import md5,sha |
13 |
+import sys |
14 |
+import string |
15 |
+import os |
16 |
+import types |
17 |
+import re |
18 |
+import signal |
19 |
+import traceback |
20 |
+import time |
21 |
|
22 |
from catalyst.defaults import verbosity, valid_config_file_values |
23 |
|
24 |
selinux_capable = False |
25 |
#userpriv_capable = (os.getuid() == 0) |
26 |
#fakeroot_capable = False |
27 |
+ |
28 |
BASH_BINARY = "/bin/bash" |
29 |
|
30 |
# set it to 0 for the soft limit, 1 for the hard limit |
31 |
@@ -25,35 +32,35 @@ spawned_pids = [] |
32 |
|
33 |
|
34 |
def cleanup(pids,block_exceptions=True): |
35 |
- """function to go through and reap the list of pids passed to it""" |
36 |
- global spawned_pids |
37 |
- if type(pids) == int: |
38 |
- pids = [pids] |
39 |
- for x in pids: |
40 |
- try: |
41 |
- os.kill(x,signal.SIGTERM) |
42 |
- if os.waitpid(x,os.WNOHANG)[1] == 0: |
43 |
- # feisty bugger, still alive. |
44 |
- os.kill(x,signal.SIGKILL) |
45 |
- os.waitpid(x,0) |
46 |
- |
47 |
- except OSError, oe: |
48 |
- if block_exceptions: |
49 |
- pass |
50 |
- if oe.errno not in (10,3): |
51 |
- raise oe |
52 |
- except SystemExit: |
53 |
- raise |
54 |
- except Exception: |
55 |
- if block_exceptions: |
56 |
- pass |
57 |
- try: spawned_pids.remove(x) |
58 |
- except IndexError: pass |
59 |
- |
60 |
- |
61 |
- |
62 |
-# a function to turn a string of non-printable characters into a string of |
63 |
-# hex characters |
64 |
+ """function to go through and reap the list of pids passed to it""" |
65 |
+ global spawned_pids |
66 |
+ if type(pids) == int: |
67 |
+ pids = [pids] |
68 |
+ for x in pids: |
69 |
+ try: |
70 |
+ os.kill(x,signal.SIGTERM) |
71 |
+ if os.waitpid(x,os.WNOHANG)[1] == 0: |
72 |
+ # feisty bugger, still alive. |
73 |
+ os.kill(x,signal.SIGKILL) |
74 |
+ os.waitpid(x,0) |
75 |
+ except OSError, oe: |
76 |
+ if block_exceptions: |
77 |
+ pass |
78 |
+ if oe.errno not in (10,3): |
79 |
+ raise oe |
80 |
+ except SystemExit: |
81 |
+ raise |
82 |
+ except Exception: |
83 |
+ if block_exceptions: |
84 |
+ pass |
85 |
+ try: |
86 |
+ spawned_pids.remove(x) |
87 |
+ except IndexError: |
88 |
+ pass |
89 |
+ |
90 |
+ |
91 |
+# a function to turn a string of non-printable characters |
92 |
+# into a string of hex characters |
93 |
def hexify(str): |
94 |
hexStr = string.hexdigits |
95 |
r = '' |
96 |
@@ -61,7 +68,6 @@ def hexify(str): |
97 |
i = ord(ch) |
98 |
r = r + hexStr[(i >> 4) & 0xF] + hexStr[i & 0xF] |
99 |
return r |
100 |
-# hexify() |
101 |
|
102 |
|
103 |
def read_from_clst(file): |
104 |
@@ -77,7 +83,6 @@ def read_from_clst(file): |
105 |
myline = myline + line |
106 |
myf.close() |
107 |
return myline |
108 |
-# read_from_clst |
109 |
|
110 |
|
111 |
def list_bashify(mylist): |
112 |
@@ -92,6 +97,7 @@ def list_bashify(mylist): |
113 |
mypack=string.join(mypack) |
114 |
return mypack |
115 |
|
116 |
+ |
117 |
def list_to_string(mylist): |
118 |
if type(mylist)==types.StringType: |
119 |
mypack=[mylist] |
120 |
@@ -104,6 +110,7 @@ def list_to_string(mylist): |
121 |
mypack=string.join(mypack) |
122 |
return mypack |
123 |
|
124 |
+ |
125 |
class CatalystError(Exception): |
126 |
def __init__(self, message): |
127 |
if message: |
128 |
@@ -115,84 +122,83 @@ class CatalystError(Exception): |
129 |
print "!!! catalyst: "+message |
130 |
print |
131 |
|
132 |
+ |
133 |
def die(msg=None): |
134 |
warn(msg) |
135 |
sys.exit(1) |
136 |
|
137 |
+ |
138 |
def warn(msg): |
139 |
print "!!! catalyst: "+msg |
140 |
|
141 |
+ |
142 |
def find_binary(myc): |
143 |
"""look through the environmental path for an executable file named whatever myc is""" |
144 |
- # this sucks. badly. |
145 |
- p=os.getenv("PATH") |
146 |
- if p == None: |
147 |
- return None |
148 |
- for x in p.split(":"): |
149 |
- #if it exists, and is executable |
150 |
- if os.path.exists("%s/%s" % (x,myc)) and os.stat("%s/%s" % (x,myc))[0] & 0x0248: |
151 |
- return "%s/%s" % (x,myc) |
152 |
- return None |
153 |
+ # this sucks. badly. |
154 |
+ p=os.getenv("PATH") |
155 |
+ if p == None: |
156 |
+ return None |
157 |
+ for x in p.split(":"): |
158 |
+ #if it exists, and is executable |
159 |
+ if os.path.exists("%s/%s" % (x,myc)) and os.stat("%s/%s" % (x,myc))[0] & 0x0248: |
160 |
+ return "%s/%s" % (x,myc) |
161 |
+ return None |
162 |
+ |
163 |
|
164 |
def spawn_bash(mycommand,env={},debug=False,opt_name=None,**keywords): |
165 |
"""spawn mycommand as an arguement to bash""" |
166 |
args=[BASH_BINARY] |
167 |
if not opt_name: |
168 |
- opt_name=mycommand.split()[0] |
169 |
+ opt_name=mycommand.split()[0] |
170 |
if "BASH_ENV" not in env: |
171 |
- env["BASH_ENV"] = "/etc/spork/is/not/valid/profile.env" |
172 |
+ env["BASH_ENV"] = "/etc/spork/is/not/valid/profile.env" |
173 |
if debug: |
174 |
- args.append("-x") |
175 |
+ args.append("-x") |
176 |
args.append("-c") |
177 |
args.append(mycommand) |
178 |
return spawn(args,env=env,opt_name=opt_name,**keywords) |
179 |
|
180 |
-#def spawn_get_output(mycommand,spawn_type=spawn,raw_exit_code=False,emulate_gso=True, \ |
181 |
-# collect_fds=[1],fd_pipes=None,**keywords): |
182 |
|
183 |
def spawn_get_output(mycommand,raw_exit_code=False,emulate_gso=True, \ |
184 |
- collect_fds=[1],fd_pipes=None,**keywords): |
185 |
- """call spawn, collecting the output to fd's specified in collect_fds list |
186 |
- emulate_gso is a compatability hack to emulate commands.getstatusoutput's return, minus the |
187 |
- requirement it always be a bash call (spawn_type controls the actual spawn call), and minus the |
188 |
- 'lets let log only stdin and let stderr slide by'. |
189 |
- |
190 |
- emulate_gso was deprecated from the day it was added, so convert your code over. |
191 |
- spawn_type is the passed in function to call- typically spawn_bash, spawn, spawn_sandbox, or spawn_fakeroot""" |
192 |
- global selinux_capable |
193 |
- pr,pw=os.pipe() |
194 |
- |
195 |
- #if type(spawn_type) not in [types.FunctionType, types.MethodType]: |
196 |
- # s="spawn_type must be passed a function, not",type(spawn_type),spawn_type |
197 |
- # raise Exception,s |
198 |
- |
199 |
- if fd_pipes==None: |
200 |
- fd_pipes={} |
201 |
- fd_pipes[0] = 0 |
202 |
- |
203 |
- for x in collect_fds: |
204 |
- fd_pipes[x] = pw |
205 |
- keywords["returnpid"]=True |
206 |
- |
207 |
- mypid=spawn_bash(mycommand,fd_pipes=fd_pipes,**keywords) |
208 |
- os.close(pw) |
209 |
- if type(mypid) != types.ListType: |
210 |
- os.close(pr) |
211 |
- return [mypid, "%s: No such file or directory" % mycommand.split()[0]] |
212 |
- |
213 |
- fd=os.fdopen(pr,"r") |
214 |
- mydata=fd.readlines() |
215 |
- fd.close() |
216 |
- if emulate_gso: |
217 |
- mydata=string.join(mydata) |
218 |
- if len(mydata) and mydata[-1] == "\n": |
219 |
- mydata=mydata[:-1] |
220 |
- retval=os.waitpid(mypid[0],0)[1] |
221 |
- cleanup(mypid) |
222 |
- if raw_exit_code: |
223 |
- return [retval,mydata] |
224 |
- retval=process_exit_code(retval) |
225 |
- return [retval, mydata] |
226 |
+ collect_fds=[1],fd_pipes=None,**keywords): |
227 |
+ """call spawn, collecting the output to fd's specified in collect_fds list |
228 |
+ emulate_gso is a compatability hack to emulate commands.getstatusoutput's return, minus the |
229 |
+ requirement it always be a bash call (spawn_type controls the actual spawn call), and minus the |
230 |
+ 'lets let log only stdin and let stderr slide by'. |
231 |
+ |
232 |
+ emulate_gso was deprecated from the day it was added, so convert your code over. |
233 |
+ spawn_type is the passed in function to call- typically spawn_bash, spawn, spawn_sandbox, or spawn_fakeroot""" |
234 |
+ global selinux_capable |
235 |
+ pr,pw=os.pipe() |
236 |
+ |
237 |
+ if fd_pipes==None: |
238 |
+ fd_pipes={} |
239 |
+ fd_pipes[0] = 0 |
240 |
+ |
241 |
+ for x in collect_fds: |
242 |
+ fd_pipes[x] = pw |
243 |
+ keywords["returnpid"]=True |
244 |
+ |
245 |
+ mypid=spawn_bash(mycommand,fd_pipes=fd_pipes,**keywords) |
246 |
+ os.close(pw) |
247 |
+ if type(mypid) != types.ListType: |
248 |
+ os.close(pr) |
249 |
+ return [mypid, "%s: No such file or directory" % mycommand.split()[0]] |
250 |
+ |
251 |
+ fd=os.fdopen(pr,"r") |
252 |
+ mydata=fd.readlines() |
253 |
+ fd.close() |
254 |
+ if emulate_gso: |
255 |
+ mydata=string.join(mydata) |
256 |
+ if len(mydata) and mydata[-1] == "\n": |
257 |
+ mydata=mydata[:-1] |
258 |
+ retval=os.waitpid(mypid[0],0)[1] |
259 |
+ cleanup(mypid) |
260 |
+ if raw_exit_code: |
261 |
+ return [retval,mydata] |
262 |
+ retval=process_exit_code(retval) |
263 |
+ return [retval, mydata] |
264 |
+ |
265 |
|
266 |
# base spawn function |
267 |
def spawn(mycommand,env={},raw_exit_code=False,opt_name=None,fd_pipes=None,returnpid=False,\ |
268 |
@@ -230,8 +236,8 @@ def spawn(mycommand,env={},raw_exit_code=False,opt_name=None,fd_pipes=None,retur |
269 |
return None |
270 |
myc = find_binary(myc) |
271 |
if myc == None: |
272 |
- return None |
273 |
- mypid=[] |
274 |
+ return None |
275 |
+ mypid=[] |
276 |
if logfile: |
277 |
pr,pw=os.pipe() |
278 |
mypid.extend(spawn(('tee','-i','-a',logfile),returnpid=True,fd_pipes={0:pr,1:1,2:2})) |
279 |
@@ -295,77 +301,77 @@ def spawn(mycommand,env={},raw_exit_code=False,opt_name=None,fd_pipes=None,retur |
280 |
if x not in trg_fd: |
281 |
try: |
282 |
os.close(x) |
283 |
- except SystemExit, e: |
284 |
- raise |
285 |
- except: |
286 |
- pass |
287 |
- |
288 |
- # note this order must be preserved- can't change gid/groups if you change uid first. |
289 |
- if selinux_capable and selinux_context: |
290 |
- import selinux |
291 |
- selinux.setexec(selinux_context) |
292 |
- if gid: |
293 |
- os.setgid(gid) |
294 |
- if groups: |
295 |
- os.setgroups(groups) |
296 |
- if uid: |
297 |
- os.setuid(uid) |
298 |
- if umask: |
299 |
- os.umask(umask) |
300 |
- else: |
301 |
- os.umask(022) |
302 |
- |
303 |
- try: |
304 |
- #print "execing", myc, myargs |
305 |
- if func_call: |
306 |
- # either use a passed in func for interpretting the results, or return if no exception. |
307 |
- # note the passed in list, and dict are expanded. |
308 |
- if len(mycommand) == 4: |
309 |
- os._exit(mycommand[3](mycommand[0](*mycommand[1],**mycommand[2]))) |
310 |
- try: |
311 |
- mycommand[0](*mycommand[1],**mycommand[2]) |
312 |
- except Exception,e: |
313 |
- print "caught exception",e," in forked func",mycommand[0] |
314 |
- sys.exit(0) |
315 |
- |
316 |
- #os.execvp(myc,myargs) |
317 |
- os.execve(myc,myargs,env) |
318 |
- except SystemExit, e: |
319 |
- raise |
320 |
- except Exception, e: |
321 |
- if not func_call: |
322 |
- raise str(e)+":\n "+myc+" "+string.join(myargs) |
323 |
- print "func call failed" |
324 |
- |
325 |
- # If the execve fails, we need to report it, and exit |
326 |
- # *carefully* --- report error here |
327 |
- os._exit(1) |
328 |
- sys.exit(1) |
329 |
- return # should never get reached |
330 |
- |
331 |
- # if we were logging, kill the pipes. |
332 |
- if logfile: |
333 |
- os.close(pr) |
334 |
- os.close(pw) |
335 |
- |
336 |
- if returnpid: |
337 |
- return mypid |
338 |
- |
339 |
- # loop through pids (typically one, unless logging), either waiting on their death, or waxing them |
340 |
- # if the main pid (mycommand) returned badly. |
341 |
- while len(mypid): |
342 |
- retval=os.waitpid(mypid[-1],0)[1] |
343 |
- if retval != 0: |
344 |
- cleanup(mypid[0:-1],block_exceptions=False) |
345 |
- # at this point we've killed all other kid pids generated via this call. |
346 |
- # return now. |
347 |
- if raw_exit_code: |
348 |
- return retval |
349 |
- return process_exit_code(retval,throw_signals=raise_signals) |
350 |
- else: |
351 |
- mypid.pop(-1) |
352 |
- cleanup(mypid) |
353 |
- return 0 |
354 |
+ except SystemExit, e: |
355 |
+ raise |
356 |
+ except: |
357 |
+ pass |
358 |
+ |
359 |
+ # note this order must be preserved- can't change gid/groups if you change uid first. |
360 |
+ if selinux_capable and selinux_context: |
361 |
+ import selinux |
362 |
+ selinux.setexec(selinux_context) |
363 |
+ if gid: |
364 |
+ os.setgid(gid) |
365 |
+ if groups: |
366 |
+ os.setgroups(groups) |
367 |
+ if uid: |
368 |
+ os.setuid(uid) |
369 |
+ if umask: |
370 |
+ os.umask(umask) |
371 |
+ else: |
372 |
+ os.umask(022) |
373 |
+ |
374 |
+ try: |
375 |
+ #print "execing", myc, myargs |
376 |
+ if func_call: |
377 |
+ # either use a passed in func for interpretting the results, or return if no exception. |
378 |
+ # note the passed in list, and dict are expanded. |
379 |
+ if len(mycommand) == 4: |
380 |
+ os._exit(mycommand[3](mycommand[0](*mycommand[1],**mycommand[2]))) |
381 |
+ try: |
382 |
+ mycommand[0](*mycommand[1],**mycommand[2]) |
383 |
+ except Exception,e: |
384 |
+ print "caught exception",e," in forked func",mycommand[0] |
385 |
+ sys.exit(0) |
386 |
+ |
387 |
+ os.execve(myc,myargs,env) |
388 |
+ except SystemExit, e: |
389 |
+ raise |
390 |
+ except Exception, e: |
391 |
+ if not func_call: |
392 |
+ raise str(e)+":\n "+myc+" "+string.join(myargs) |
393 |
+ print "func call failed" |
394 |
+ |
395 |
+ # If the execve fails, we need to report it, and exit |
396 |
+ # *carefully* --- report error here |
397 |
+ os._exit(1) |
398 |
+ sys.exit(1) |
399 |
+ return # should never get reached |
400 |
+ |
401 |
+ # if we were logging, kill the pipes. |
402 |
+ if logfile: |
403 |
+ os.close(pr) |
404 |
+ os.close(pw) |
405 |
+ |
406 |
+ if returnpid: |
407 |
+ return mypid |
408 |
+ |
409 |
+ # loop through pids (typically one, unless logging), either waiting on their death, or waxing them |
410 |
+ # if the main pid (mycommand) returned badly. |
411 |
+ while len(mypid): |
412 |
+ retval=os.waitpid(mypid[-1],0)[1] |
413 |
+ if retval != 0: |
414 |
+ cleanup(mypid[0:-1],block_exceptions=False) |
415 |
+ # at this point we've killed all other kid pids generated via this call. |
416 |
+ # return now. |
417 |
+ if raw_exit_code: |
418 |
+ return retval |
419 |
+ return process_exit_code(retval,throw_signals=raise_signals) |
420 |
+ else: |
421 |
+ mypid.pop(-1) |
422 |
+ cleanup(mypid) |
423 |
+ return 0 |
424 |
+ |
425 |
|
426 |
def cmd(mycmd,myexc="",env={}): |
427 |
try: |
428 |
@@ -376,19 +382,21 @@ def cmd(mycmd,myexc="",env={}): |
429 |
except: |
430 |
raise |
431 |
|
432 |
+ |
433 |
def process_exit_code(retval,throw_signals=False): |
434 |
- """process a waitpid returned exit code, returning exit code if it exit'd, or the |
435 |
- signal if it died from signalling |
436 |
- if throw_signals is on, it raises a SystemExit if the process was signaled. |
437 |
- This is intended for usage with threads, although at the moment you can't signal individual |
438 |
- threads in python, only the master thread, so it's a questionable option.""" |
439 |
- if (retval & 0xff)==0: |
440 |
- return retval >> 8 # return exit code |
441 |
- else: |
442 |
- if throw_signals: |
443 |
- #use systemexit, since portage is stupid about exception catching. |
444 |
- raise SystemExit() |
445 |
- return (retval & 0xff) << 8 # interrupted by signal |
446 |
+ """process a waitpid returned exit code, returning exit code if it exit'd, or the |
447 |
+ signal if it died from signalling |
448 |
+ if throw_signals is on, it raises a SystemExit if the process was signaled. |
449 |
+ This is intended for usage with threads, although at the moment you can't signal individual |
450 |
+ threads in python, only the master thread, so it's a questionable option.""" |
451 |
+ if (retval & 0xff)==0: |
452 |
+ return retval >> 8 # return exit code |
453 |
+ else: |
454 |
+ if throw_signals: |
455 |
+ #use systemexit, since portage is stupid about exception catching. |
456 |
+ raise SystemExit() |
457 |
+ return (retval & 0xff) << 8 # interrupted by signal |
458 |
+ |
459 |
|
460 |
def file_locate(settings,filelist,expand=1): |
461 |
#if expand=1, non-absolute paths will be accepted and |
462 |
@@ -398,15 +406,18 @@ def file_locate(settings,filelist,expand=1): |
463 |
#filenames such as cdtar are optional, so we don't assume the variable is defined. |
464 |
pass |
465 |
else: |
466 |
- if len(settings[myfile])==0: |
467 |
- raise CatalystError, "File variable \""+myfile+"\" has a length of zero (not specified.)" |
468 |
- if settings[myfile][0]=="/": |
469 |
- if not os.path.exists(settings[myfile]): |
470 |
- raise CatalystError, "Cannot locate specified "+myfile+": "+settings[myfile] |
471 |
- elif expand and os.path.exists(os.getcwd()+"/"+settings[myfile]): |
472 |
- settings[myfile]=os.getcwd()+"/"+settings[myfile] |
473 |
- else: |
474 |
- raise CatalystError, "Cannot locate specified "+myfile+": "+settings[myfile]+" (2nd try)" |
475 |
+ if len(settings[myfile])==0: |
476 |
+ raise CatalystError("File variable \"" + myfile + |
477 |
+ "\" has a length of zero (not specified.)") |
478 |
+ if settings[myfile][0]=="/": |
479 |
+ if not os.path.exists(settings[myfile]): |
480 |
+ raise CatalystError("Cannot locate specified " + myfile + |
481 |
+ ": "+settings[myfile]) |
482 |
+ elif expand and os.path.exists(os.getcwd()+"/"+settings[myfile]): |
483 |
+ settings[myfile]=os.getcwd()+"/"+settings[myfile] |
484 |
+ else: |
485 |
+ raise CatalystError("Cannot locate specified " + myfile + |
486 |
+ ": "+settings[myfile]+" (2nd try)" + |
487 |
""" |
488 |
Spec file format: |
489 |
|
490 |
@@ -427,6 +438,8 @@ that the order of multiple-value items is preserved, but the order that the item |
491 |
defined are not preserved. In other words, "foo", "bar", "oni" ordering is preserved but "item1" |
492 |
"item2" "item3" ordering is not, as the item strings are stored in a dictionary (hash). |
493 |
""" |
494 |
+ ) |
495 |
+ |
496 |
|
497 |
def parse_makeconf(mylines): |
498 |
mymakeconf={} |
499 |
@@ -450,6 +463,7 @@ def parse_makeconf(mylines): |
500 |
mymakeconf[mobj.group(1)]=clean_string |
501 |
return mymakeconf |
502 |
|
503 |
+ |
504 |
def read_makeconf(mymakeconffile): |
505 |
if os.path.exists(mymakeconffile): |
506 |
try: |
507 |
@@ -475,10 +489,12 @@ def read_makeconf(mymakeconffile): |
508 |
makeconf={} |
509 |
return makeconf |
510 |
|
511 |
+ |
512 |
def msg(mymsg,verblevel=1): |
513 |
if verbosity>=verblevel: |
514 |
print mymsg |
515 |
|
516 |
+ |
517 |
def pathcompare(path1,path2): |
518 |
# Change double slashes to slash |
519 |
path1 = re.sub(r"//",r"/",path1) |
520 |
@@ -491,6 +507,7 @@ def pathcompare(path1,path2): |
521 |
return 1 |
522 |
return 0 |
523 |
|
524 |
+ |
525 |
def ismount(path): |
526 |
"enhanced to handle bind mounts" |
527 |
if os.path.ismount(path): |
528 |
@@ -504,6 +521,7 @@ def ismount(path): |
529 |
return 1 |
530 |
return 0 |
531 |
|
532 |
+ |
533 |
def addl_arg_parse(myspec,addlargs,requiredspec,validspec): |
534 |
"helper function to help targets parse additional arguments" |
535 |
global valid_config_file_values |
536 |
@@ -522,6 +540,7 @@ def addl_arg_parse(myspec,addlargs,requiredspec,validspec): |
537 |
if messages: |
538 |
raise CatalystError, '\n\tAlso: '.join(messages) |
539 |
|
540 |
+ |
541 |
def touch(myfile): |
542 |
try: |
543 |
myf=open(myfile,"w") |
544 |
@@ -529,8 +548,9 @@ def touch(myfile): |
545 |
except IOError: |
546 |
raise CatalystError, "Could not touch "+myfile+"." |
547 |
|
548 |
+ |
549 |
def countdown(secs=5, doing="Starting"): |
550 |
- if secs: |
551 |
+ if secs: |
552 |
print ">>> Waiting",secs,"seconds before starting..." |
553 |
print ">>> (Control-C to abort)...\n"+doing+" in: ", |
554 |
ticks=range(secs) |
555 |
@@ -541,14 +561,15 @@ def countdown(secs=5, doing="Starting"): |
556 |
time.sleep(1) |
557 |
print |
558 |
|
559 |
+ |
560 |
def normpath(mypath): |
561 |
TrailingSlash=False |
562 |
- if mypath[-1] == "/": |
563 |
- TrailingSlash=True |
564 |
- newpath = os.path.normpath(mypath) |
565 |
- if len(newpath) > 1: |
566 |
- if newpath[:2] == "//": |
567 |
- newpath = newpath[1:] |
568 |
+ if mypath[-1] == "/": |
569 |
+ TrailingSlash=True |
570 |
+ newpath = os.path.normpath(mypath) |
571 |
+ if len(newpath) > 1: |
572 |
+ if newpath[:2] == "//": |
573 |
+ newpath = newpath[1:] |
574 |
if TrailingSlash: |
575 |
- newpath=newpath+'/' |
576 |
- return newpath |
577 |
+ newpath=newpath+'/' |
578 |
+ return newpath |
579 |
-- |
580 |
2.1.0 |