summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/utils.py')
-rw-r--r--bitbake/lib/bb/utils.py315
1 files changed, 256 insertions, 59 deletions
diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py
index b282d09abf..ebee65d3dd 100644
--- a/bitbake/lib/bb/utils.py
+++ b/bitbake/lib/bb/utils.py
@@ -13,10 +13,12 @@ import errno
13import logging 13import logging
14import bb 14import bb
15import bb.msg 15import bb.msg
16import locale
16import multiprocessing 17import multiprocessing
17import fcntl 18import fcntl
18import importlib 19import importlib
19from importlib import machinery 20import importlib.machinery
21import importlib.util
20import itertools 22import itertools
21import subprocess 23import subprocess
22import glob 24import glob
@@ -26,6 +28,11 @@ import errno
26import signal 28import signal
27import collections 29import collections
28import copy 30import copy
31import ctypes
32import random
33import socket
34import struct
35import tempfile
29from subprocess import getstatusoutput 36from subprocess import getstatusoutput
30from contextlib import contextmanager 37from contextlib import contextmanager
31from ctypes import cdll 38from ctypes import cdll
@@ -43,7 +50,7 @@ def clean_context():
43 50
44def get_context(): 51def get_context():
45 return _context 52 return _context
46 53
47 54
48def set_context(ctx): 55def set_context(ctx):
49 _context = ctx 56 _context = ctx
@@ -205,8 +212,8 @@ def explode_dep_versions2(s, *, sort=True):
205 inversion = True 212 inversion = True
206 # This list is based on behavior and supported comparisons from deb, opkg and rpm. 213 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
207 # 214 #
208 # Even though =<, <<, ==, !=, =>, and >> may not be supported, 215 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
209 # we list each possibly valid item. 216 # we list each possibly valid item.
210 # The build system is responsible for validation of what it supports. 217 # The build system is responsible for validation of what it supports.
211 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')): 218 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
212 lastcmp = i[0:2] 219 lastcmp = i[0:2]
@@ -251,7 +258,7 @@ def explode_dep_versions(s):
251 """ 258 """
252 Take an RDEPENDS style string of format: 259 Take an RDEPENDS style string of format:
253 "DEPEND1 (optional version) DEPEND2 (optional version) ..." 260 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
254 skip null value and items appeared in dependancy string multiple times 261 skip null value and items appeared in dependency string multiple times
255 and return a dictionary of dependencies and versions. 262 and return a dictionary of dependencies and versions.
256 """ 263 """
257 r = explode_dep_versions2(s) 264 r = explode_dep_versions2(s)
@@ -340,7 +347,7 @@ def _print_exception(t, value, tb, realfile, text, context):
340 exception = traceback.format_exception_only(t, value) 347 exception = traceback.format_exception_only(t, value)
341 error.append('Error executing a python function in %s:\n' % realfile) 348 error.append('Error executing a python function in %s:\n' % realfile)
342 349
343 # Strip 'us' from the stack (better_exec call) unless that was where the 350 # Strip 'us' from the stack (better_exec call) unless that was where the
344 # error came from 351 # error came from
345 if tb.tb_next is not None: 352 if tb.tb_next is not None:
346 tb = tb.tb_next 353 tb = tb.tb_next
@@ -379,7 +386,7 @@ def _print_exception(t, value, tb, realfile, text, context):
379 386
380 error.append("Exception: %s" % ''.join(exception)) 387 error.append("Exception: %s" % ''.join(exception))
381 388
382 # If the exception is from spwaning a task, let's be helpful and display 389 # If the exception is from spawning a task, let's be helpful and display
383 # the output (which hopefully includes stderr). 390 # the output (which hopefully includes stderr).
384 if isinstance(value, subprocess.CalledProcessError) and value.output: 391 if isinstance(value, subprocess.CalledProcessError) and value.output:
385 error.append("Subprocess output:") 392 error.append("Subprocess output:")
@@ -400,7 +407,7 @@ def better_exec(code, context, text = None, realfile = "<code>", pythonexception
400 code = better_compile(code, realfile, realfile) 407 code = better_compile(code, realfile, realfile)
401 try: 408 try:
402 exec(code, get_context(), context) 409 exec(code, get_context(), context)
403 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError): 410 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError, bb.process.ExecutionError):
404 # Error already shown so passthrough, no need for traceback 411 # Error already shown so passthrough, no need for traceback
405 raise 412 raise
406 except Exception as e: 413 except Exception as e:
@@ -427,12 +434,14 @@ def better_eval(source, locals, extraglobals = None):
427 return eval(source, ctx, locals) 434 return eval(source, ctx, locals)
428 435
429@contextmanager 436@contextmanager
430def fileslocked(files): 437def fileslocked(files, *args, **kwargs):
431 """Context manager for locking and unlocking file locks.""" 438 """Context manager for locking and unlocking file locks."""
432 locks = [] 439 locks = []
433 if files: 440 if files:
434 for lockfile in files: 441 for lockfile in files:
435 locks.append(bb.utils.lockfile(lockfile)) 442 l = bb.utils.lockfile(lockfile, *args, **kwargs)
443 if l is not None:
444 locks.append(l)
436 445
437 try: 446 try:
438 yield 447 yield
@@ -451,9 +460,16 @@ def lockfile(name, shared=False, retry=True, block=False):
451 consider the possibility of sending a signal to the process to break 460 consider the possibility of sending a signal to the process to break
452 out - at which point you want block=True rather than retry=True. 461 out - at which point you want block=True rather than retry=True.
453 """ 462 """
463 basename = os.path.basename(name)
464 if len(basename) > 255:
465 root, ext = os.path.splitext(basename)
466 basename = root[:255 - len(ext)] + ext
467
454 dirname = os.path.dirname(name) 468 dirname = os.path.dirname(name)
455 mkdirhier(dirname) 469 mkdirhier(dirname)
456 470
471 name = os.path.join(dirname, basename)
472
457 if not os.access(dirname, os.W_OK): 473 if not os.access(dirname, os.W_OK):
458 logger.error("Unable to acquire lock '%s', directory is not writable", 474 logger.error("Unable to acquire lock '%s', directory is not writable",
459 name) 475 name)
@@ -487,7 +503,7 @@ def lockfile(name, shared=False, retry=True, block=False):
487 return lf 503 return lf
488 lf.close() 504 lf.close()
489 except OSError as e: 505 except OSError as e:
490 if e.errno == errno.EACCES: 506 if e.errno == errno.EACCES or e.errno == errno.ENAMETOOLONG:
491 logger.error("Unable to acquire lock '%s', %s", 507 logger.error("Unable to acquire lock '%s', %s",
492 e.strerror, name) 508 e.strerror, name)
493 sys.exit(1) 509 sys.exit(1)
@@ -532,7 +548,12 @@ def md5_file(filename):
532 Return the hex string representation of the MD5 checksum of filename. 548 Return the hex string representation of the MD5 checksum of filename.
533 """ 549 """
534 import hashlib 550 import hashlib
535 return _hasher(hashlib.md5(), filename) 551 try:
552 sig = hashlib.new('MD5', usedforsecurity=False)
553 except TypeError:
554 # Some configurations don't appear to support two arguments
555 sig = hashlib.new('MD5')
556 return _hasher(sig, filename)
536 557
537def sha256_file(filename): 558def sha256_file(filename):
538 """ 559 """
@@ -583,11 +604,25 @@ def preserved_envvars():
583 v = [ 604 v = [
584 'BBPATH', 605 'BBPATH',
585 'BB_PRESERVE_ENV', 606 'BB_PRESERVE_ENV',
586 'BB_ENV_WHITELIST', 607 'BB_ENV_PASSTHROUGH_ADDITIONS',
587 'BB_ENV_EXTRAWHITE',
588 ] 608 ]
589 return v + preserved_envvars_exported() 609 return v + preserved_envvars_exported()
590 610
611def check_system_locale():
612 """Make sure the required system locale are available and configured"""
613 default_locale = locale.getlocale(locale.LC_CTYPE)
614
615 try:
616 locale.setlocale(locale.LC_CTYPE, ("en_US", "UTF-8"))
617 except:
618 sys.exit("Please make sure locale 'en_US.UTF-8' is available on your system")
619 else:
620 locale.setlocale(locale.LC_CTYPE, default_locale)
621
622 if sys.getfilesystemencoding() != "utf-8":
623 sys.exit("Please use a locale setting which supports UTF-8 (such as LANG=en_US.UTF-8).\n"
624 "Python can't change the filesystem locale after loading so we need a UTF-8 when Python starts or things won't work.")
625
591def filter_environment(good_vars): 626def filter_environment(good_vars):
592 """ 627 """
593 Create a pristine environment for bitbake. This will remove variables that 628 Create a pristine environment for bitbake. This will remove variables that
@@ -615,21 +650,21 @@ def filter_environment(good_vars):
615 650
616def approved_variables(): 651def approved_variables():
617 """ 652 """
618 Determine and return the list of whitelisted variables which are approved 653 Determine and return the list of variables which are approved
619 to remain in the environment. 654 to remain in the environment.
620 """ 655 """
621 if 'BB_PRESERVE_ENV' in os.environ: 656 if 'BB_PRESERVE_ENV' in os.environ:
622 return os.environ.keys() 657 return os.environ.keys()
623 approved = [] 658 approved = []
624 if 'BB_ENV_WHITELIST' in os.environ: 659 if 'BB_ENV_PASSTHROUGH' in os.environ:
625 approved = os.environ['BB_ENV_WHITELIST'].split() 660 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
626 approved.extend(['BB_ENV_WHITELIST']) 661 approved.extend(['BB_ENV_PASSTHROUGH'])
627 else: 662 else:
628 approved = preserved_envvars() 663 approved = preserved_envvars()
629 if 'BB_ENV_EXTRAWHITE' in os.environ: 664 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
630 approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split()) 665 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
631 if 'BB_ENV_EXTRAWHITE' not in approved: 666 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
632 approved.extend(['BB_ENV_EXTRAWHITE']) 667 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
633 return approved 668 return approved
634 669
635def clean_environment(): 670def clean_environment():
@@ -683,8 +718,8 @@ def remove(path, recurse=False, ionice=False):
683 return 718 return
684 if recurse: 719 if recurse:
685 for name in glob.glob(path): 720 for name in glob.glob(path):
686 if _check_unsafe_delete_path(path): 721 if _check_unsafe_delete_path(name):
687 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path) 722 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % name)
688 # shutil.rmtree(name) would be ideal but its too slow 723 # shutil.rmtree(name) would be ideal but its too slow
689 cmd = [] 724 cmd = []
690 if ionice: 725 if ionice:
@@ -710,9 +745,9 @@ def prunedir(topdir, ionice=False):
710# but thats possibly insane and suffixes is probably going to be small 745# but thats possibly insane and suffixes is probably going to be small
711# 746#
712def prune_suffix(var, suffixes, d): 747def prune_suffix(var, suffixes, d):
713 """ 748 """
714 See if var ends with any of the suffixes listed and 749 See if var ends with any of the suffixes listed and
715 remove it if found 750 remove it if found
716 """ 751 """
717 for suffix in suffixes: 752 for suffix in suffixes:
718 if suffix and var.endswith(suffix): 753 if suffix and var.endswith(suffix):
@@ -723,7 +758,8 @@ def mkdirhier(directory):
723 """Create a directory like 'mkdir -p', but does not complain if 758 """Create a directory like 'mkdir -p', but does not complain if
724 directory already exists like os.makedirs 759 directory already exists like os.makedirs
725 """ 760 """
726 761 if '${' in str(directory):
762 bb.fatal("Directory name {} contains unexpanded bitbake variable. This may cause build failures and WORKDIR polution.".format(directory))
727 try: 763 try:
728 os.makedirs(directory) 764 os.makedirs(directory)
729 except OSError as e: 765 except OSError as e:
@@ -742,7 +778,7 @@ def movefile(src, dest, newmtime = None, sstat = None):
742 if not sstat: 778 if not sstat:
743 sstat = os.lstat(src) 779 sstat = os.lstat(src)
744 except Exception as e: 780 except Exception as e:
745 print("movefile: Stating source file failed...", e) 781 logger.warning("movefile: Stating source file failed...", e)
746 return None 782 return None
747 783
748 destexists = 1 784 destexists = 1
@@ -770,7 +806,7 @@ def movefile(src, dest, newmtime = None, sstat = None):
770 os.unlink(src) 806 os.unlink(src)
771 return os.lstat(dest) 807 return os.lstat(dest)
772 except Exception as e: 808 except Exception as e:
773 print("movefile: failed to properly create symlink:", dest, "->", target, e) 809 logger.warning("movefile: failed to properly create symlink:", dest, "->", target, e)
774 return None 810 return None
775 811
776 renamefailed = 1 812 renamefailed = 1
@@ -782,12 +818,12 @@ def movefile(src, dest, newmtime = None, sstat = None):
782 818
783 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]: 819 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
784 try: 820 try:
785 os.rename(src, destpath) 821 bb.utils.rename(src, destpath)
786 renamefailed = 0 822 renamefailed = 0
787 except Exception as e: 823 except Exception as e:
788 if e.errno != errno.EXDEV: 824 if e.errno != errno.EXDEV:
789 # Some random error. 825 # Some random error.
790 print("movefile: Failed to move", src, "to", dest, e) 826 logger.warning("movefile: Failed to move", src, "to", dest, e)
791 return None 827 return None
792 # Invalid cross-device-link 'bind' mounted or actually Cross-Device 828 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
793 829
@@ -796,16 +832,16 @@ def movefile(src, dest, newmtime = None, sstat = None):
796 if stat.S_ISREG(sstat[stat.ST_MODE]): 832 if stat.S_ISREG(sstat[stat.ST_MODE]):
797 try: # For safety copy then move it over. 833 try: # For safety copy then move it over.
798 shutil.copyfile(src, destpath + "#new") 834 shutil.copyfile(src, destpath + "#new")
799 os.rename(destpath + "#new", destpath) 835 bb.utils.rename(destpath + "#new", destpath)
800 didcopy = 1 836 didcopy = 1
801 except Exception as e: 837 except Exception as e:
802 print('movefile: copy', src, '->', dest, 'failed.', e) 838 logger.warning('movefile: copy', src, '->', dest, 'failed.', e)
803 return None 839 return None
804 else: 840 else:
805 #we don't yet handle special, so we need to fall back to /bin/mv 841 #we don't yet handle special, so we need to fall back to /bin/mv
806 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'") 842 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
807 if a[0] != 0: 843 if a[0] != 0:
808 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a) 844 logger.warning("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
809 return None # failure 845 return None # failure
810 try: 846 try:
811 if didcopy: 847 if didcopy:
@@ -813,7 +849,7 @@ def movefile(src, dest, newmtime = None, sstat = None):
813 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown 849 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
814 os.unlink(src) 850 os.unlink(src)
815 except Exception as e: 851 except Exception as e:
816 print("movefile: Failed to chown/chmod/unlink", dest, e) 852 logger.warning("movefile: Failed to chown/chmod/unlink", dest, e)
817 return None 853 return None
818 854
819 if newmtime: 855 if newmtime:
@@ -874,7 +910,7 @@ def copyfile(src, dest, newmtime = None, sstat = None):
874 910
875 # For safety copy then move it over. 911 # For safety copy then move it over.
876 shutil.copyfile(src, dest + "#new") 912 shutil.copyfile(src, dest + "#new")
877 os.rename(dest + "#new", dest) 913 bb.utils.rename(dest + "#new", dest)
878 except Exception as e: 914 except Exception as e:
879 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e)) 915 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
880 return False 916 return False
@@ -965,13 +1001,16 @@ def umask(new_mask):
965 os.umask(current_mask) 1001 os.umask(current_mask)
966 1002
967def to_boolean(string, default=None): 1003def to_boolean(string, default=None):
968 """ 1004 """
969 Check input string and return boolean value True/False/None 1005 Check input string and return boolean value True/False/None
970 depending upon the checks 1006 depending upon the checks
971 """ 1007 """
972 if not string: 1008 if not string:
973 return default 1009 return default
974 1010
1011 if isinstance(string, int):
1012 return string != 0
1013
975 normalized = string.lower() 1014 normalized = string.lower()
976 if normalized in ("y", "yes", "1", "true"): 1015 if normalized in ("y", "yes", "1", "true"):
977 return True 1016 return True
@@ -1103,7 +1142,10 @@ def get_referenced_vars(start_expr, d):
1103 1142
1104 1143
1105def cpu_count(): 1144def cpu_count():
1106 return multiprocessing.cpu_count() 1145 try:
1146 return len(os.sched_getaffinity(0))
1147 except OSError:
1148 return multiprocessing.cpu_count()
1107 1149
1108def nonblockingfd(fd): 1150def nonblockingfd(fd):
1109 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) 1151 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
@@ -1178,7 +1220,7 @@ def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1178 variables: a list of variable names to look for. Functions 1220 variables: a list of variable names to look for. Functions
1179 may also be specified, but must be specified with '()' at 1221 may also be specified, but must be specified with '()' at
1180 the end of the name. Note that the function doesn't have 1222 the end of the name. Note that the function doesn't have
1181 any intrinsic understanding of _append, _prepend, _remove, 1223 any intrinsic understanding of :append, :prepend, :remove,
1182 or overrides, so these are considered as part of the name. 1224 or overrides, so these are considered as part of the name.
1183 These values go into a regular expression, so regular 1225 These values go into a regular expression, so regular
1184 expression syntax is allowed. 1226 expression syntax is allowed.
@@ -1590,33 +1632,89 @@ def set_process_name(name):
1590 except: 1632 except:
1591 pass 1633 pass
1592 1634
1635def enable_loopback_networking():
1636 # From bits/ioctls.h
1637 SIOCGIFFLAGS = 0x8913
1638 SIOCSIFFLAGS = 0x8914
1639 SIOCSIFADDR = 0x8916
1640 SIOCSIFNETMASK = 0x891C
1641
1642 # if.h
1643 IFF_UP = 0x1
1644 IFF_RUNNING = 0x40
1645
1646 # bits/socket.h
1647 AF_INET = 2
1648
1649 # char ifr_name[IFNAMSIZ=16]
1650 ifr_name = struct.pack("@16s", b"lo")
1651 def netdev_req(fd, req, data = b""):
1652 # Pad and add interface name
1653 data = ifr_name + data + (b'\x00' * (16 - len(data)))
1654 # Return all data after interface name
1655 return fcntl.ioctl(fd, req, data)[16:]
1656
1657 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP) as sock:
1658 fd = sock.fileno()
1659
1660 # struct sockaddr_in ifr_addr { unsigned short family; uint16_t sin_port ; uint32_t in_addr; }
1661 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 127, 0, 0, 1)
1662 netdev_req(fd, SIOCSIFADDR, req)
1663
1664 # short ifr_flags
1665 flags = struct.unpack_from('@h', netdev_req(fd, SIOCGIFFLAGS))[0]
1666 flags |= IFF_UP | IFF_RUNNING
1667 netdev_req(fd, SIOCSIFFLAGS, struct.pack('@h', flags))
1668
1669 # struct sockaddr_in ifr_netmask
1670 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 255, 0, 0, 0)
1671 netdev_req(fd, SIOCSIFNETMASK, req)
1672
1673def disable_network(uid=None, gid=None):
1674 """
1675 Disable networking in the current process if the kernel supports it, else
1676 just return after logging to debug. To do this we need to create a new user
1677 namespace, then map back to the original uid/gid.
1678 """
1679 libc = ctypes.CDLL('libc.so.6')
1680
1681 # From sched.h
1682 # New user namespace
1683 CLONE_NEWUSER = 0x10000000
1684 # New network namespace
1685 CLONE_NEWNET = 0x40000000
1686
1687 if uid is None:
1688 uid = os.getuid()
1689 if gid is None:
1690 gid = os.getgid()
1691
1692 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1693 if ret != 0:
1694 logger.debug("System doesn't support disabling network without admin privs")
1695 return
1696 with open("/proc/self/uid_map", "w") as f:
1697 f.write("%s %s 1" % (uid, uid))
1698 with open("/proc/self/setgroups", "w") as f:
1699 f.write("deny")
1700 with open("/proc/self/gid_map", "w") as f:
1701 f.write("%s %s 1" % (gid, gid))
1702
1593def export_proxies(d): 1703def export_proxies(d):
1704 from bb.fetch2 import get_fetcher_environment
1594 """ export common proxies variables from datastore to environment """ 1705 """ export common proxies variables from datastore to environment """
1595 import os 1706 newenv = get_fetcher_environment(d)
1596 1707 for v in newenv:
1597 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY', 1708 os.environ[v] = newenv[v]
1598 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1599 'GIT_PROXY_COMMAND']
1600 exported = False
1601
1602 for v in variables:
1603 if v in os.environ.keys():
1604 exported = True
1605 else:
1606 v_proxy = d.getVar(v)
1607 if v_proxy is not None:
1608 os.environ[v] = v_proxy
1609 exported = True
1610
1611 return exported
1612
1613 1709
1614def load_plugins(logger, plugins, pluginpath): 1710def load_plugins(logger, plugins, pluginpath):
1615 def load_plugin(name): 1711 def load_plugin(name):
1616 logger.debug('Loading plugin %s' % name) 1712 logger.debug('Loading plugin %s' % name)
1617 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] ) 1713 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1618 if spec: 1714 if spec:
1619 return spec.loader.load_module() 1715 mod = importlib.util.module_from_spec(spec)
1716 spec.loader.exec_module(mod)
1717 return mod
1620 1718
1621 logger.debug('Loading plugins from %s...' % pluginpath) 1719 logger.debug('Loading plugins from %s...' % pluginpath)
1622 1720
@@ -1669,3 +1767,102 @@ def is_semver(version):
1669 return False 1767 return False
1670 1768
1671 return True 1769 return True
1770
1771# Wrapper around os.rename which can handle cross device problems
1772# e.g. from container filesystems
1773def rename(src, dst):
1774 try:
1775 os.rename(src, dst)
1776 except OSError as err:
1777 if err.errno == 18:
1778 # Invalid cross-device link error
1779 shutil.move(src, dst)
1780 else:
1781 raise err
1782
1783@contextmanager
1784def environment(**envvars):
1785 """
1786 Context manager to selectively update the environment with the specified mapping.
1787 """
1788 backup = dict(os.environ)
1789 try:
1790 os.environ.update(envvars)
1791 yield
1792 finally:
1793 for var in envvars:
1794 if var in backup:
1795 os.environ[var] = backup[var]
1796 elif var in os.environ:
1797 del os.environ[var]
1798
1799def is_local_uid(uid=''):
1800 """
1801 Check whether uid is a local one or not.
1802 Can't use pwd module since it gets all UIDs, not local ones only.
1803 """
1804 if not uid:
1805 uid = os.getuid()
1806 with open('/etc/passwd', 'r') as f:
1807 for line in f:
1808 line_split = line.split(':')
1809 if len(line_split) < 3:
1810 continue
1811 if str(uid) == line_split[2]:
1812 return True
1813 return False
1814
1815def mkstemp(suffix=None, prefix=None, dir=None, text=False):
1816 """
1817 Generates a unique filename, independent of time.
1818
1819 mkstemp() in glibc (at least) generates unique file names based on the
1820 current system time. When combined with highly parallel builds, and
1821 operating over NFS (e.g. shared sstate/downloads) this can result in
1822 conflicts and race conditions.
1823
1824 This function adds additional entropy to the file name so that a collision
1825 is independent of time and thus extremely unlikely.
1826 """
1827 entropy = "".join(random.choices("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", k=20))
1828 if prefix:
1829 prefix = prefix + entropy
1830 else:
1831 prefix = tempfile.gettempprefix() + entropy
1832 return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text)
1833
1834def path_is_descendant(descendant, ancestor):
1835 """
1836 Returns True if the path `descendant` is a descendant of `ancestor`
1837 (including being equivalent to `ancestor` itself). Otherwise returns False.
1838 Correctly accounts for symlinks, bind mounts, etc. by using
1839 os.path.samestat() to compare paths
1840
1841 May raise any exception that os.stat() raises
1842 """
1843
1844 ancestor_stat = os.stat(ancestor)
1845
1846 # Recurse up each directory component of the descendant to see if it is
1847 # equivalent to the ancestor
1848 check_dir = os.path.abspath(descendant).rstrip("/")
1849 while check_dir:
1850 check_stat = os.stat(check_dir)
1851 if os.path.samestat(check_stat, ancestor_stat):
1852 return True
1853 check_dir = os.path.dirname(check_dir).rstrip("/")
1854
1855 return False
1856
1857# If we don't have a timeout of some kind and a process/thread exits badly (for example
1858# OOM killed) and held a lock, we'd just hang in the lock futex forever. It is better
1859# we exit at some point than hang. 5 minutes with no progress means we're probably deadlocked.
1860@contextmanager
1861def lock_timeout(lock):
1862 held = lock.acquire(timeout=5*60)
1863 try:
1864 if not held:
1865 os._exit(1)
1866 yield held
1867 finally:
1868 lock.release()