utils.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. # ex:ts=4:sw=4:sts=4:et
  2. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  3. """
  4. BitBake Utility Functions
  5. """
  6. # Copyright (C) 2004 Michael Lauer
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License version 2 as
  10. # published by the Free Software Foundation.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. import re, fcntl, os, string, stat, shutil, time
  21. import sys
  22. import bb
  23. import errno
  24. import bb.msg
  25. from commands import getstatusoutput
  26. # Version comparison
  27. separators = ".-"
  28. # Context used in better_exec, eval
  29. _context = {
  30. "os": os,
  31. "bb": bb,
  32. "time": time,
  33. }
  34. def explode_version(s):
  35. r = []
  36. alpha_regexp = re.compile('^([a-zA-Z]+)(.*)$')
  37. numeric_regexp = re.compile('^(\d+)(.*)$')
  38. while (s != ''):
  39. if s[0] in string.digits:
  40. m = numeric_regexp.match(s)
  41. r.append(int(m.group(1)))
  42. s = m.group(2)
  43. continue
  44. if s[0] in string.letters:
  45. m = alpha_regexp.match(s)
  46. r.append(m.group(1))
  47. s = m.group(2)
  48. continue
  49. r.append(s[0])
  50. s = s[1:]
  51. return r
  52. def vercmp_part(a, b):
  53. va = explode_version(a)
  54. vb = explode_version(b)
  55. sa = False
  56. sb = False
  57. while True:
  58. if va == []:
  59. ca = None
  60. else:
  61. ca = va.pop(0)
  62. if vb == []:
  63. cb = None
  64. else:
  65. cb = vb.pop(0)
  66. if ca == None and cb == None:
  67. return 0
  68. if isinstance(ca, basestring):
  69. sa = ca in separators
  70. if isinstance(cb, basestring):
  71. sb = cb in separators
  72. if sa and not sb:
  73. return -1
  74. if not sa and sb:
  75. return 1
  76. if ca > cb:
  77. return 1
  78. if ca < cb:
  79. return -1
  80. def vercmp(ta, tb):
  81. (ea, va, ra) = ta
  82. (eb, vb, rb) = tb
  83. r = int(ea)-int(eb)
  84. if (r == 0):
  85. r = vercmp_part(va, vb)
  86. if (r == 0):
  87. r = vercmp_part(ra, rb)
  88. return r
  89. _package_weights_ = {"pre":-2, "p":0, "alpha":-4, "beta":-3, "rc":-1} # dicts are unordered
  90. _package_ends_ = ["pre", "p", "alpha", "beta", "rc", "cvs", "bk", "HEAD" ] # so we need ordered list
  91. def relparse(myver):
  92. """Parses the last elements of a version number into a triplet, that can
  93. later be compared.
  94. """
  95. number = 0
  96. p1 = 0
  97. p2 = 0
  98. mynewver = myver.split('_')
  99. if len(mynewver) == 2:
  100. # an _package_weights_
  101. number = float(mynewver[0])
  102. match = 0
  103. for x in _package_ends_:
  104. elen = len(x)
  105. if mynewver[1][:elen] == x:
  106. match = 1
  107. p1 = _package_weights_[x]
  108. try:
  109. p2 = float(mynewver[1][elen:])
  110. except:
  111. p2 = 0
  112. break
  113. if not match:
  114. # normal number or number with letter at end
  115. divider = len(myver)-1
  116. if myver[divider:] not in "1234567890":
  117. # letter at end
  118. p1 = ord(myver[divider:])
  119. number = float(myver[0:divider])
  120. else:
  121. number = float(myver)
  122. else:
  123. # normal number or number with letter at end
  124. divider = len(myver)-1
  125. if myver[divider:] not in "1234567890":
  126. #letter at end
  127. p1 = ord(myver[divider:])
  128. number = float(myver[0:divider])
  129. else:
  130. number = float(myver)
  131. return [number, p1, p2]
  132. __vercmp_cache__ = {}
  133. def vercmp_string(val1, val2):
  134. """This takes two version strings and returns an integer to tell you whether
  135. the versions are the same, val1>val2 or val2>val1.
  136. """
  137. # quick short-circuit
  138. if val1 == val2:
  139. return 0
  140. valkey = val1 + " " + val2
  141. # cache lookup
  142. try:
  143. return __vercmp_cache__[valkey]
  144. try:
  145. return - __vercmp_cache__[val2 + " " + val1]
  146. except KeyError:
  147. pass
  148. except KeyError:
  149. pass
  150. # consider 1_p2 vc 1.1
  151. # after expansion will become (1_p2,0) vc (1,1)
  152. # then 1_p2 is compared with 1 before 0 is compared with 1
  153. # to solve the bug we need to convert it to (1,0_p2)
  154. # by splitting _prepart part and adding it back _after_expansion
  155. val1_prepart = val2_prepart = ''
  156. if val1.count('_'):
  157. val1, val1_prepart = val1.split('_', 1)
  158. if val2.count('_'):
  159. val2, val2_prepart = val2.split('_', 1)
  160. # replace '-' by '.'
  161. # FIXME: Is it needed? can val1/2 contain '-'?
  162. val1 = val1.split("-")
  163. if len(val1) == 2:
  164. val1[0] = val1[0] + "." + val1[1]
  165. val2 = val2.split("-")
  166. if len(val2) == 2:
  167. val2[0] = val2[0] + "." + val2[1]
  168. val1 = val1[0].split('.')
  169. val2 = val2[0].split('.')
  170. # add back decimal point so that .03 does not become "3" !
  171. for x in range(1, len(val1)):
  172. if val1[x][0] == '0' :
  173. val1[x] = '.' + val1[x]
  174. for x in range(1, len(val2)):
  175. if val2[x][0] == '0' :
  176. val2[x] = '.' + val2[x]
  177. # extend varion numbers
  178. if len(val2) < len(val1):
  179. val2.extend(["0"]*(len(val1)-len(val2)))
  180. elif len(val1) < len(val2):
  181. val1.extend(["0"]*(len(val2)-len(val1)))
  182. # add back _prepart tails
  183. if val1_prepart:
  184. val1[-1] += '_' + val1_prepart
  185. if val2_prepart:
  186. val2[-1] += '_' + val2_prepart
  187. # The above code will extend version numbers out so they
  188. # have the same number of digits.
  189. for x in range(0, len(val1)):
  190. cmp1 = relparse(val1[x])
  191. cmp2 = relparse(val2[x])
  192. for y in range(0, 3):
  193. myret = cmp1[y] - cmp2[y]
  194. if myret != 0:
  195. __vercmp_cache__[valkey] = myret
  196. return myret
  197. __vercmp_cache__[valkey] = 0
  198. return 0
  199. def explode_deps(s):
  200. """
  201. Take an RDEPENDS style string of format:
  202. "DEPEND1 (optional version) DEPEND2 (optional version) ..."
  203. and return a list of dependencies.
  204. Version information is ignored.
  205. """
  206. r = []
  207. l = s.split()
  208. flag = False
  209. for i in l:
  210. if i[0] == '(':
  211. flag = True
  212. #j = []
  213. if not flag:
  214. r.append(i)
  215. #else:
  216. # j.append(i)
  217. if flag and i.endswith(')'):
  218. flag = False
  219. # Ignore version
  220. #r[-1] += ' ' + ' '.join(j)
  221. return r
  222. def explode_dep_versions(s):
  223. """
  224. Take an RDEPENDS style string of format:
  225. "DEPEND1 (optional version) DEPEND2 (optional version) ..."
  226. and return a dictionary of dependencies and versions.
  227. """
  228. r = {}
  229. l = s.split()
  230. lastdep = None
  231. lastver = ""
  232. inversion = False
  233. for i in l:
  234. if i[0] == '(':
  235. inversion = True
  236. lastver = i[1:] or ""
  237. #j = []
  238. elif inversion and i.endswith(')'):
  239. inversion = False
  240. lastver = lastver + " " + (i[:-1] or "")
  241. r[lastdep] = lastver
  242. elif not inversion:
  243. r[i] = None
  244. lastdep = i
  245. lastver = ""
  246. elif inversion:
  247. lastver = lastver + " " + i
  248. return r
  249. def join_deps(deps):
  250. """
  251. Take the result from explode_dep_versions and generate a dependency string
  252. """
  253. result = []
  254. for dep in deps:
  255. if deps[dep]:
  256. result.append(dep + " (" + deps[dep] + ")")
  257. else:
  258. result.append(dep)
  259. return ", ".join(result)
  260. def _print_trace(body, line):
  261. """
  262. Print the Environment of a Text Body
  263. """
  264. # print the environment of the method
  265. bb.msg.error(bb.msg.domain.Util, "Printing the environment of the function")
  266. min_line = max(1, line-4)
  267. max_line = min(line + 4, len(body)-1)
  268. for i in range(min_line, max_line + 1):
  269. bb.msg.error(bb.msg.domain.Util, "\t%.4d:%s" % (i, body[i-1]) )
  270. def better_compile(text, file, realfile, mode = "exec"):
  271. """
  272. A better compile method. This method
  273. will print the offending lines.
  274. """
  275. try:
  276. return compile(text, file, mode)
  277. except Exception as e:
  278. # split the text into lines again
  279. body = text.split('\n')
  280. bb.msg.error(bb.msg.domain.Util, "Error in compiling python function in: %s" % (realfile))
  281. bb.msg.error(bb.msg.domain.Util, "The lines leading to this error were:")
  282. bb.msg.error(bb.msg.domain.Util, "\t%d:%s:'%s'" % (e.lineno, e.__class__.__name__, body[e.lineno-1]))
  283. _print_trace(body, e.lineno)
  284. # exit now
  285. sys.exit(1)
  286. def better_exec(code, context, text, realfile):
  287. """
  288. Similiar to better_compile, better_exec will
  289. print the lines that are responsible for the
  290. error.
  291. """
  292. import bb.parse
  293. try:
  294. exec(code, _context, context)
  295. except:
  296. (t, value, tb) = sys.exc_info()
  297. if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
  298. raise
  299. # print the Header of the Error Message
  300. bb.msg.error(bb.msg.domain.Util, "Error in executing python function in: %s" % realfile)
  301. bb.msg.error(bb.msg.domain.Util, "Exception:%s Message:%s" % (t, value))
  302. # let us find the line number now
  303. while tb.tb_next:
  304. tb = tb.tb_next
  305. import traceback
  306. line = traceback.tb_lineno(tb)
  307. _print_trace( text.split('\n'), line )
  308. raise
  309. def simple_exec(code, context):
  310. exec(code, _context, context)
  311. def better_eval(source, locals):
  312. return eval(source, _context, locals)
  313. def lockfile(name):
  314. """
  315. Use the file fn as a lock file, return when the lock has been acquired.
  316. Returns a variable to pass to unlockfile().
  317. """
  318. path = os.path.dirname(name)
  319. if not os.path.isdir(path):
  320. bb.msg.error(bb.msg.domain.Util, "Error, lockfile path does not exist!: %s" % path)
  321. sys.exit(1)
  322. while True:
  323. # If we leave the lockfiles lying around there is no problem
  324. # but we should clean up after ourselves. This gives potential
  325. # for races though. To work around this, when we acquire the lock
  326. # we check the file we locked was still the lock file on disk.
  327. # by comparing inode numbers. If they don't match or the lockfile
  328. # no longer exists, we start again.
  329. # This implementation is unfair since the last person to request the
  330. # lock is the most likely to win it.
  331. try:
  332. lf = open(name, "a + ")
  333. fcntl.flock(lf.fileno(), fcntl.LOCK_EX)
  334. statinfo = os.fstat(lf.fileno())
  335. if os.path.exists(lf.name):
  336. statinfo2 = os.stat(lf.name)
  337. if statinfo.st_ino == statinfo2.st_ino:
  338. return lf
  339. # File no longer exists or changed, retry
  340. lf.close
  341. except Exception as e:
  342. continue
  343. def unlockfile(lf):
  344. """
  345. Unlock a file locked using lockfile()
  346. """
  347. os.unlink(lf.name)
  348. fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
  349. lf.close
  350. def md5_file(filename):
  351. """
  352. Return the hex string representation of the MD5 checksum of filename.
  353. """
  354. try:
  355. import hashlib
  356. m = hashlib.md5()
  357. except ImportError:
  358. import md5
  359. m = md5.new()
  360. for line in open(filename):
  361. m.update(line)
  362. return m.hexdigest()
  363. def sha256_file(filename):
  364. """
  365. Return the hex string representation of the 256-bit SHA checksum of
  366. filename. On Python 2.4 this will return None, so callers will need to
  367. handle that by either skipping SHA checks, or running a standalone sha256sum
  368. binary.
  369. """
  370. try:
  371. import hashlib
  372. except ImportError:
  373. return None
  374. s = hashlib.sha256()
  375. for line in open(filename):
  376. s.update(line)
  377. return s.hexdigest()
  378. def preserved_envvars_list():
  379. return [
  380. 'BBPATH',
  381. 'BB_PRESERVE_ENV',
  382. 'BB_ENV_WHITELIST',
  383. 'BB_ENV_EXTRAWHITE',
  384. 'COLORTERM',
  385. 'DBUS_SESSION_BUS_ADDRESS',
  386. 'DESKTOP_SESSION',
  387. 'DESKTOP_STARTUP_ID',
  388. 'DISPLAY',
  389. 'GNOME_KEYRING_PID',
  390. 'GNOME_KEYRING_SOCKET',
  391. 'GPG_AGENT_INFO',
  392. 'GTK_RC_FILES',
  393. 'HOME',
  394. 'LANG',
  395. 'LOGNAME',
  396. 'PATH',
  397. 'PWD',
  398. 'SESSION_MANAGER',
  399. 'SHELL',
  400. 'SSH_AUTH_SOCK',
  401. 'TERM',
  402. 'USER',
  403. 'USERNAME',
  404. '_',
  405. 'XAUTHORITY',
  406. 'XDG_DATA_DIRS',
  407. 'XDG_SESSION_COOKIE',
  408. ]
  409. def filter_environment(good_vars):
  410. """
  411. Create a pristine environment for bitbake. This will remove variables that
  412. are not known and may influence the build in a negative way.
  413. """
  414. removed_vars = []
  415. for key in os.environ.keys():
  416. if key in good_vars:
  417. continue
  418. removed_vars.append(key)
  419. os.unsetenv(key)
  420. del os.environ[key]
  421. if len(removed_vars):
  422. bb.msg.debug(1, bb.msg.domain.Util, "Removed the following variables from the environment: %s" % (", ".join(removed_vars)))
  423. return removed_vars
  424. def clean_environment():
  425. """
  426. Clean up any spurious environment variables. This will remove any
  427. variables the user hasn't chose to preserve.
  428. """
  429. if 'BB_PRESERVE_ENV' not in os.environ:
  430. if 'BB_ENV_WHITELIST' in os.environ:
  431. good_vars = os.environ['BB_ENV_WHITELIST'].split()
  432. else:
  433. good_vars = preserved_envvars_list()
  434. if 'BB_ENV_EXTRAWHITE' in os.environ:
  435. good_vars.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
  436. filter_environment(good_vars)
  437. def empty_environment():
  438. """
  439. Remove all variables from the environment.
  440. """
  441. for s in os.environ.keys():
  442. os.unsetenv(s)
  443. del os.environ[s]
  444. def build_environment(d):
  445. """
  446. Build an environment from all exported variables.
  447. """
  448. import bb.data
  449. for var in bb.data.keys(d):
  450. export = bb.data.getVarFlag(var, "export", d)
  451. if export:
  452. os.environ[var] = bb.data.getVar(var, d, True) or ""
  453. def prunedir(topdir):
  454. # Delete everything reachable from the directory named in 'topdir'.
  455. # CAUTION: This is dangerous!
  456. for root, dirs, files in os.walk(topdir, topdown = False):
  457. for name in files:
  458. os.remove(os.path.join(root, name))
  459. for name in dirs:
  460. if os.path.islink(os.path.join(root, name)):
  461. os.remove(os.path.join(root, name))
  462. else:
  463. os.rmdir(os.path.join(root, name))
  464. os.rmdir(topdir)
  465. #
  466. # Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
  467. # but thats possibly insane and suffixes is probably going to be small
  468. #
  469. def prune_suffix(var, suffixes, d):
  470. # See if var ends with any of the suffixes listed and
  471. # remove it if found
  472. for suffix in suffixes:
  473. if var.endswith(suffix):
  474. return var.replace(suffix, "")
  475. return var
  476. def mkdirhier(dir):
  477. """Create a directory like 'mkdir -p', but does not complain if
  478. directory already exists like os.makedirs
  479. """
  480. bb.msg.debug(3, bb.msg.domain.Util, "mkdirhier(%s)" % dir)
  481. try:
  482. os.makedirs(dir)
  483. bb.msg.debug(2, bb.msg.domain.Util, "created " + dir)
  484. except OSError as e:
  485. if e.errno != errno.EEXIST:
  486. raise e
  487. def movefile(src, dest, newmtime = None, sstat = None):
  488. """Moves a file from src to dest, preserving all permissions and
  489. attributes; mtime will be preserved even when moving across
  490. filesystems. Returns true on success and false on failure. Move is
  491. atomic.
  492. """
  493. #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
  494. try:
  495. if not sstat:
  496. sstat = os.lstat(src)
  497. except Exception as e:
  498. print("movefile: Stating source file failed...", e)
  499. return None
  500. destexists = 1
  501. try:
  502. dstat = os.lstat(dest)
  503. except:
  504. dstat = os.lstat(os.path.dirname(dest))
  505. destexists = 0
  506. if destexists:
  507. if stat.S_ISLNK(dstat[stat.ST_MODE]):
  508. try:
  509. os.unlink(dest)
  510. destexists = 0
  511. except Exception as e:
  512. pass
  513. if stat.S_ISLNK(sstat[stat.ST_MODE]):
  514. try:
  515. target = os.readlink(src)
  516. if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
  517. os.unlink(dest)
  518. os.symlink(target, dest)
  519. #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
  520. os.unlink(src)
  521. return os.lstat(dest)
  522. except Exception as e:
  523. print("movefile: failed to properly create symlink:", dest, "->", target, e)
  524. return None
  525. renamefailed = 1
  526. if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
  527. try:
  528. os.rename(src, dest)
  529. renamefailed = 0
  530. except Exception as e:
  531. if e[0] != errno.EXDEV:
  532. # Some random error.
  533. print("movefile: Failed to move", src, "to", dest, e)
  534. return None
  535. # Invalid cross-device-link 'bind' mounted or actually Cross-Device
  536. if renamefailed:
  537. didcopy = 0
  538. if stat.S_ISREG(sstat[stat.ST_MODE]):
  539. try: # For safety copy then move it over.
  540. shutil.copyfile(src, dest + "#new")
  541. os.rename(dest + "#new", dest)
  542. didcopy = 1
  543. except Exception as e:
  544. print('movefile: copy', src, '->', dest, 'failed.', e)
  545. return None
  546. else:
  547. #we don't yet handle special, so we need to fall back to /bin/mv
  548. a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
  549. if a[0] != 0:
  550. print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
  551. return None # failure
  552. try:
  553. if didcopy:
  554. os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
  555. os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
  556. os.unlink(src)
  557. except Exception as e:
  558. print("movefile: Failed to chown/chmod/unlink", dest, e)
  559. return None
  560. if newmtime:
  561. os.utime(dest, (newmtime, newmtime))
  562. else:
  563. os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
  564. newmtime = sstat[stat.ST_MTIME]
  565. return newmtime
  566. def copyfile(src, dest, newmtime = None, sstat = None):
  567. """
  568. Copies a file from src to dest, preserving all permissions and
  569. attributes; mtime will be preserved even when moving across
  570. filesystems. Returns true on success and false on failure.
  571. """
  572. #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
  573. try:
  574. if not sstat:
  575. sstat = os.lstat(src)
  576. except Exception as e:
  577. print("copyfile: Stating source file failed...", e)
  578. return False
  579. destexists = 1
  580. try:
  581. dstat = os.lstat(dest)
  582. except:
  583. dstat = os.lstat(os.path.dirname(dest))
  584. destexists = 0
  585. if destexists:
  586. if stat.S_ISLNK(dstat[stat.ST_MODE]):
  587. try:
  588. os.unlink(dest)
  589. destexists = 0
  590. except Exception as e:
  591. pass
  592. if stat.S_ISLNK(sstat[stat.ST_MODE]):
  593. try:
  594. target = os.readlink(src)
  595. if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
  596. os.unlink(dest)
  597. os.symlink(target, dest)
  598. #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
  599. return os.lstat(dest)
  600. except Exception as e:
  601. print("copyfile: failed to properly create symlink:", dest, "->", target, e)
  602. return False
  603. if stat.S_ISREG(sstat[stat.ST_MODE]):
  604. try: # For safety copy then move it over.
  605. shutil.copyfile(src, dest + "#new")
  606. os.rename(dest + "#new", dest)
  607. except Exception as e:
  608. print('copyfile: copy', src, '->', dest, 'failed.', e)
  609. return False
  610. else:
  611. #we don't yet handle special, so we need to fall back to /bin/mv
  612. a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
  613. if a[0] != 0:
  614. print("copyfile: Failed to copy special file:" + src + "' to '" + dest + "'", a)
  615. return False # failure
  616. try:
  617. os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
  618. os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
  619. except Exception as e:
  620. print("copyfile: Failed to chown/chmod/unlink", dest, e)
  621. return False
  622. if newmtime:
  623. os.utime(dest, (newmtime, newmtime))
  624. else:
  625. os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
  626. newmtime = sstat[stat.ST_MTIME]
  627. return newmtime
  628. def which(path, item, direction = 0):
  629. """
  630. Locate a file in a PATH
  631. """
  632. paths = (path or "").split(':')
  633. if direction != 0:
  634. paths.reverse()
  635. for p in paths:
  636. next = os.path.join(p, item)
  637. if os.path.exists(next):
  638. return next
  639. return ""
  640. def init_logger(logger, verbose, debug, debug_domains):
  641. """
  642. Set verbosity and debug levels in the logger
  643. """
  644. if verbose:
  645. logger.set_verbose(True)
  646. if debug:
  647. logger.set_debug_level(debug)
  648. else:
  649. logger.set_debug_level(0)
  650. if debug_domains:
  651. logger.set_debug_domains(debug_domains)