diff options
Diffstat (limited to 'bitbake/lib/bb/utils.py')
-rw-r--r-- | bitbake/lib/bb/utils.py | 315 |
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 | |||
13 | import logging | 13 | import logging |
14 | import bb | 14 | import bb |
15 | import bb.msg | 15 | import bb.msg |
16 | import locale | ||
16 | import multiprocessing | 17 | import multiprocessing |
17 | import fcntl | 18 | import fcntl |
18 | import importlib | 19 | import importlib |
19 | from importlib import machinery | 20 | import importlib.machinery |
21 | import importlib.util | ||
20 | import itertools | 22 | import itertools |
21 | import subprocess | 23 | import subprocess |
22 | import glob | 24 | import glob |
@@ -26,6 +28,11 @@ import errno | |||
26 | import signal | 28 | import signal |
27 | import collections | 29 | import collections |
28 | import copy | 30 | import copy |
31 | import ctypes | ||
32 | import random | ||
33 | import socket | ||
34 | import struct | ||
35 | import tempfile | ||
29 | from subprocess import getstatusoutput | 36 | from subprocess import getstatusoutput |
30 | from contextlib import contextmanager | 37 | from contextlib import contextmanager |
31 | from ctypes import cdll | 38 | from ctypes import cdll |
@@ -43,7 +50,7 @@ def clean_context(): | |||
43 | 50 | ||
44 | def get_context(): | 51 | def get_context(): |
45 | return _context | 52 | return _context |
46 | 53 | ||
47 | 54 | ||
48 | def set_context(ctx): | 55 | def 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 |
430 | def fileslocked(files): | 437 | def 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 | ||
537 | def sha256_file(filename): | 558 | def 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 | ||
611 | def 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 | |||
591 | def filter_environment(good_vars): | 626 | def 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 | ||
616 | def approved_variables(): | 651 | def 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 | ||
635 | def clean_environment(): | 670 | def 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 | # |
712 | def prune_suffix(var, suffixes, d): | 747 | def 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 | ||
967 | def to_boolean(string, default=None): | 1003 | def 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 | ||
1105 | def cpu_count(): | 1144 | def 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 | ||
1108 | def nonblockingfd(fd): | 1150 | def 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 | ||
1635 | def 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 | |||
1673 | def 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 | |||
1593 | def export_proxies(d): | 1703 | def 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 | ||
1614 | def load_plugins(logger, plugins, pluginpath): | 1710 | def 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 | ||
1773 | def 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 | ||
1784 | def 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 | |||
1799 | def 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 | |||
1815 | def 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 | |||
1834 | def 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 | ||
1861 | def 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() | ||