xref: /openbmc/openbmc/poky/bitbake/lib/bb/fetch2/__init__.py (revision 5562ba89cfc493f292271e21f46e71b2ec6915b1)
1"""
2BitBake 'Fetch' implementations
3
4Classes for obtaining upstream sources for the
5BitBake build tools.
6"""
7
8# Copyright (C) 2003, 2004  Chris Larson
9# Copyright (C) 2012  Intel Corporation
10#
11# SPDX-License-Identifier: GPL-2.0-only
12#
13# Based on functions from the base bb module, Copyright 2003 Holger Schurig
14
15import os, re
16import signal
17import logging
18import urllib.request, urllib.parse, urllib.error
19if 'git' not in urllib.parse.uses_netloc:
20    urllib.parse.uses_netloc.append('git')
21import operator
22import collections
23import subprocess
24import pickle
25import errno
26import bb.utils
27import bb.checksum
28import bb.process
29import bb.event
30
31__version__ = "2"
32_checksum_cache = bb.checksum.FileChecksumCache()
33_revisions_cache = bb.checksum.RevisionsCache()
34
35logger = logging.getLogger("BitBake.Fetcher")
36
37CHECKSUM_LIST = [ "goh1", "md5", "sha256", "sha1", "sha384", "sha512" ]
38SHOWN_CHECKSUM_LIST = ["sha256"]
39
40class BBFetchException(Exception):
41    """Class all fetch exceptions inherit from"""
42    def __init__(self, message):
43        self.msg = message
44        Exception.__init__(self, message)
45
46    def __str__(self):
47        return self.msg
48
49class UntrustedUrl(BBFetchException):
50    """Exception raised when encountering a host not listed in BB_ALLOWED_NETWORKS"""
51    def __init__(self, url, message=''):
52        if message:
53            msg = message
54        else:
55            msg = "The URL: '%s' is not trusted and cannot be used" % url
56        self.url = url
57        BBFetchException.__init__(self, msg)
58        self.args = (url,)
59
60class MalformedUrl(BBFetchException):
61    """Exception raised when encountering an invalid url"""
62    def __init__(self, url, message=''):
63        if message:
64            msg = message
65        else:
66            msg = "The URL: '%s' is invalid and cannot be interpreted" % url
67        self.url = url
68        BBFetchException.__init__(self, msg)
69        self.args = (url,)
70
71class FetchError(BBFetchException):
72    """General fetcher exception when something happens incorrectly"""
73    def __init__(self, message, url = None):
74        if url:
75            msg = "Fetcher failure for URL: '%s'. %s" % (url, message)
76        else:
77            msg = "Fetcher failure: %s" % message
78        self.url = url
79        BBFetchException.__init__(self, msg)
80        self.args = (message, url)
81
82class ChecksumError(FetchError):
83    """Exception when mismatched checksum encountered"""
84    def __init__(self, message, url = None, checksum = None):
85        self.checksum = checksum
86        FetchError.__init__(self, message, url)
87
88class NoChecksumError(FetchError):
89    """Exception when no checksum is specified, but BB_STRICT_CHECKSUM is set"""
90
91class UnpackError(BBFetchException):
92    """General fetcher exception when something happens incorrectly when unpacking"""
93    def __init__(self, message, url):
94        msg = "Unpack failure for URL: '%s'. %s" % (url, message)
95        self.url = url
96        BBFetchException.__init__(self, msg)
97        self.args = (message, url)
98
99class NoMethodError(BBFetchException):
100    """Exception raised when there is no method to obtain a supplied url or set of urls"""
101    def __init__(self, url):
102        msg = "Could not find a fetcher which supports the URL: '%s'" % url
103        self.url = url
104        BBFetchException.__init__(self, msg)
105        self.args = (url,)
106
107class MissingParameterError(BBFetchException):
108    """Exception raised when a fetch method is missing a critical parameter in the url"""
109    def __init__(self, missing, url):
110        msg = "URL: '%s' is missing the required parameter '%s'" % (url, missing)
111        self.url = url
112        self.missing = missing
113        BBFetchException.__init__(self, msg)
114        self.args = (missing, url)
115
116class ParameterError(BBFetchException):
117    """Exception raised when a url cannot be processed due to invalid parameters."""
118    def __init__(self, message, url):
119        msg = "URL: '%s' has invalid parameters. %s" % (url, message)
120        self.url = url
121        BBFetchException.__init__(self, msg)
122        self.args = (message, url)
123
124class NetworkAccess(BBFetchException):
125    """Exception raised when network access is disabled but it is required."""
126    def __init__(self, url, cmd):
127        msg = "Network access disabled through BB_NO_NETWORK (or set indirectly due to use of BB_FETCH_PREMIRRORONLY) but access requested with command %s (for url %s)" % (cmd, url)
128        self.url = url
129        self.cmd = cmd
130        BBFetchException.__init__(self, msg)
131        self.args = (url, cmd)
132
133class NonLocalMethod(Exception):
134    def __init__(self):
135        Exception.__init__(self)
136
137class MissingChecksumEvent(bb.event.Event):
138    def __init__(self, url, **checksums):
139        self.url = url
140        self.checksums = checksums
141        bb.event.Event.__init__(self)
142
143
144class URI(object):
145    """
146    A class representing a generic URI, with methods for
147    accessing the URI components, and stringifies to the
148    URI.
149
150    It is constructed by calling it with a URI, or setting
151    the attributes manually:
152
153     uri = URI("http://example.com/")
154
155     uri = URI()
156     uri.scheme = 'http'
157     uri.hostname = 'example.com'
158     uri.path = '/'
159
160    It has the following attributes:
161
162      * scheme (read/write)
163      * userinfo (authentication information) (read/write)
164        * username (read/write)
165        * password (read/write)
166
167        Note, password is deprecated as of RFC 3986.
168
169      * hostname (read/write)
170      * port (read/write)
171      * hostport (read only)
172        "hostname:port", if both are set, otherwise just "hostname"
173      * path (read/write)
174      * path_quoted (read/write)
175        A URI quoted version of path
176      * params (dict) (read/write)
177      * query (dict) (read/write)
178      * relative (bool) (read only)
179        True if this is a "relative URI", (e.g. file:foo.diff)
180
181    It stringifies to the URI itself.
182
183    Some notes about relative URIs: while it's specified that
184    a URI beginning with <scheme>:// should either be directly
185    followed by a hostname or a /, the old URI handling of the
186    fetch2 library did not conform to this. Therefore, this URI
187    class has some kludges to make sure that URIs are parsed in
188    a way comforming to bitbake's current usage. This URI class
189    supports the following:
190
191     file:relative/path.diff (IETF compliant)
192     git:relative/path.git (IETF compliant)
193     git:///absolute/path.git (IETF compliant)
194     file:///absolute/path.diff (IETF compliant)
195
196     file://relative/path.diff (not IETF compliant)
197
198    But it does not support the following:
199
200     file://hostname/absolute/path.diff (would be IETF compliant)
201
202    Note that the last case only applies to a list of
203    explicitly allowed schemes (currently only file://), that requires
204    its URIs to not have a network location.
205    """
206
207    _relative_schemes = ['file', 'git']
208    _netloc_forbidden = ['file']
209
210    def __init__(self, uri=None):
211        self.scheme = ''
212        self.userinfo = ''
213        self.hostname = ''
214        self.port = None
215        self._path = ''
216        self.params = {}
217        self.query = {}
218        self.relative = False
219
220        if not uri:
221            return
222
223        # We hijack the URL parameters, since the way bitbake uses
224        # them are not quite RFC compliant.
225        uri, param_str = (uri.split(";", 1) + [None])[:2]
226
227        urlp = urllib.parse.urlparse(uri)
228        self.scheme = urlp.scheme
229
230        reparse = 0
231
232        # Coerce urlparse to make URI scheme use netloc
233        if not self.scheme in urllib.parse.uses_netloc:
234            urllib.parse.uses_params.append(self.scheme)
235            reparse = 1
236
237        # Make urlparse happy(/ier) by converting local resources
238        # to RFC compliant URL format. E.g.:
239        #   file://foo.diff -> file:foo.diff
240        if urlp.scheme in self._netloc_forbidden:
241            uri = re.sub(r"(?<=:)//(?!/)", "", uri, count=1)
242            reparse = 1
243
244        if reparse:
245            urlp = urllib.parse.urlparse(uri)
246
247        # Identify if the URI is relative or not
248        if urlp.scheme in self._relative_schemes and \
249           re.compile(r"^\w+:(?!//)").match(uri):
250            self.relative = True
251
252        if not self.relative:
253            self.hostname = urlp.hostname or ''
254            self.port = urlp.port
255
256            self.userinfo += urlp.username or ''
257
258            if urlp.password:
259                self.userinfo += ':%s' % urlp.password
260
261        self.path = urllib.parse.unquote(urlp.path)
262
263        if param_str:
264            self.params = self._param_str_split(param_str, ";")
265        if urlp.query:
266            self.query = self._param_str_split(urlp.query, "&")
267
268    def __str__(self):
269        userinfo = self.userinfo
270        if userinfo:
271            userinfo += '@'
272
273        return "%s:%s%s%s%s%s%s" % (
274            self.scheme,
275            '' if self.relative else '//',
276            userinfo,
277            self.hostport,
278            self.path_quoted,
279            self._query_str(),
280            self._param_str())
281
282    def _param_str(self):
283        return (
284            ''.join([';', self._param_str_join(self.params, ";")])
285            if self.params else '')
286
287    def _query_str(self):
288        return (
289            ''.join(['?', self._param_str_join(self.query, "&")])
290            if self.query else '')
291
292    def _param_str_split(self, string, elmdelim, kvdelim="="):
293        ret = collections.OrderedDict()
294        for k, v in [x.split(kvdelim, 1) if kvdelim in x else (x, None) for x in string.split(elmdelim) if x]:
295            ret[k] = v
296        return ret
297
298    def _param_str_join(self, dict_, elmdelim, kvdelim="="):
299        return elmdelim.join([kvdelim.join([k, v]) if v else k for k, v in dict_.items()])
300
301    @property
302    def hostport(self):
303        if not self.port:
304            return self.hostname
305        return "%s:%d" % (self.hostname, self.port)
306
307    @property
308    def path_quoted(self):
309        return urllib.parse.quote(self.path)
310
311    @path_quoted.setter
312    def path_quoted(self, path):
313        self.path = urllib.parse.unquote(path)
314
315    @property
316    def path(self):
317        return self._path
318
319    @path.setter
320    def path(self, path):
321        self._path = path
322
323        if not path or re.compile("^/").match(path):
324            self.relative = False
325        else:
326            self.relative = True
327
328    @property
329    def username(self):
330        if self.userinfo:
331            return (self.userinfo.split(":", 1))[0]
332        return ''
333
334    @username.setter
335    def username(self, username):
336        password = self.password
337        self.userinfo = username
338        if password:
339            self.userinfo += ":%s" % password
340
341    @property
342    def password(self):
343        if self.userinfo and ":" in self.userinfo:
344            return (self.userinfo.split(":", 1))[1]
345        return ''
346
347    @password.setter
348    def password(self, password):
349        self.userinfo = "%s:%s" % (self.username, password)
350
351def decodeurl(url):
352    """Decodes an URL into the tokens (scheme, network location, path,
353    user, password, parameters).
354    """
355
356    uri = URI(url)
357    path = uri.path if uri.path else "/"
358    return uri.scheme, uri.hostport, path, uri.username, uri.password, uri.params
359
360def decodemirrorurl(url):
361    """Decodes a mirror URL into the tokens (scheme, network location, path,
362    user, password, parameters).
363    """
364    m = re.compile('(?P<type>[^:]*)://((?P<user>[^/;]+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
365    if not m:
366        raise MalformedUrl(url)
367
368    type = m.group('type')
369    location = m.group('location')
370    if not location:
371        raise MalformedUrl(url)
372    user = m.group('user')
373    parm = m.group('parm')
374
375    locidx = location.find('/')
376    if locidx != -1 and type.lower() != 'file':
377        host = location[:locidx]
378        path = location[locidx:]
379    elif type.lower() == 'file':
380        host = ""
381        path = location
382        if user:
383            path = user + '@' + path
384            user = ""
385    else:
386        host = location
387        path = "/"
388    if user:
389        m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
390        if m:
391            user = m.group('user')
392            pswd = m.group('pswd')
393    else:
394        user = ''
395        pswd = ''
396
397    p = collections.OrderedDict()
398    if parm:
399        for s in parm.split(';'):
400            if s:
401                if not '=' in s:
402                    raise MalformedUrl(url, "The URL: '%s' is invalid: parameter %s does not specify a value (missing '=')" % (url, s))
403                s1, s2 = s.split('=', 1)
404                p[s1] = s2
405
406    return type, host, urllib.parse.unquote(path), user, pswd, p
407
408def encodeurl(decoded):
409    """Encodes a URL from tokens (scheme, network location, path,
410    user, password, parameters).
411    """
412
413    type, host, path, user, pswd, p = decoded
414
415    if not type:
416        raise MissingParameterError('type', "encoded from the data %s" % str(decoded))
417    uri = URI()
418    uri.scheme = type
419    if user and type != "file":
420        uri.username = user
421        if pswd:
422            uri.password = pswd
423    if host and type != "file":
424        uri.hostname = host
425    if path:
426        # Standardise path to ensure comparisons work
427        while '//' in path:
428            path = path.replace("//", "/")
429        uri.path = path
430        if type == "file":
431            # Use old not IETF compliant style
432            uri.relative = False
433    if p:
434        uri.params = p
435
436    return str(uri)
437
438def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
439    if not ud.url or not uri_find or not uri_replace:
440        logger.error("uri_replace: passed an undefined value, not replacing")
441        return None
442    uri_decoded = list(decodemirrorurl(ud.url))
443    uri_find_decoded = list(decodemirrorurl(uri_find))
444    uri_replace_decoded = list(decodemirrorurl(uri_replace))
445    logger.debug2("For url %s comparing %s to %s" % (uri_decoded, uri_find_decoded, uri_replace_decoded))
446    result_decoded = ['', '', '', '', '', {}]
447    # 0 - type, 1 - host, 2 - path, 3 - user,  4- pswd, 5 - params
448    for loc, i in enumerate(uri_find_decoded):
449        result_decoded[loc] = uri_decoded[loc]
450        regexp = i
451        if loc == 0 and regexp and not regexp.endswith("$"):
452            # Leaving the type unanchored can mean "https" matching "file" can become "files"
453            # which is clearly undesirable.
454            regexp += "$"
455        if loc == 5:
456            # Handle URL parameters
457            if i:
458                # Any specified URL parameters must match
459                for k in uri_find_decoded[loc]:
460                    if uri_decoded[loc][k] != uri_find_decoded[loc][k]:
461                        return None
462            # Overwrite any specified replacement parameters
463            for k in uri_replace_decoded[loc]:
464                for l in replacements:
465                    uri_replace_decoded[loc][k] = uri_replace_decoded[loc][k].replace(l, replacements[l])
466                result_decoded[loc][k] = uri_replace_decoded[loc][k]
467        elif (loc == 3 or loc == 4) and uri_replace_decoded[loc]:
468            # User/password in the replacement is just a straight replacement
469            result_decoded[loc] = uri_replace_decoded[loc]
470        elif (re.match(regexp, uri_decoded[loc])):
471            if not uri_replace_decoded[loc]:
472                result_decoded[loc] = ""
473            else:
474                for k in replacements:
475                    uri_replace_decoded[loc] = uri_replace_decoded[loc].replace(k, replacements[k])
476                #bb.note("%s %s %s" % (regexp, uri_replace_decoded[loc], uri_decoded[loc]))
477                result_decoded[loc] = re.sub(regexp, uri_replace_decoded[loc], uri_decoded[loc], count=1)
478            if loc == 2:
479                # Handle path manipulations
480                basename = None
481                if uri_decoded[0] != uri_replace_decoded[0] and mirrortarball:
482                    # If the source and destination url types differ, must be a mirrortarball mapping
483                    basename = os.path.basename(mirrortarball)
484                    # Kill parameters, they make no sense for mirror tarballs
485                    uri_decoded[5] = {}
486                    uri_find_decoded[5] = {}
487                elif ud.localpath and ud.method.supports_checksum(ud):
488                    basename = os.path.basename(ud.localpath)
489                if basename:
490                    uri_basename = os.path.basename(uri_decoded[loc])
491                    # Prefix with a slash as a sentinel in case
492                    # result_decoded[loc] does not contain one.
493                    path = "/" + result_decoded[loc]
494                    if uri_basename and basename != uri_basename and path.endswith("/" + uri_basename):
495                        result_decoded[loc] = path[1:-len(uri_basename)] + basename
496                    elif not path.endswith("/" + basename):
497                        result_decoded[loc] = os.path.join(path[1:], basename)
498        else:
499            return None
500    result = encodeurl(result_decoded)
501    if result == ud.url:
502        return None
503    logger.debug2("For url %s returning %s" % (ud.url, result))
504    return result
505
506methods = []
507urldata_cache = {}
508saved_headrevs = {}
509
510def fetcher_init(d, servercontext=True):
511    """
512    Called to initialize the fetchers once the configuration data is known.
513    Calls before this must not hit the cache.
514    """
515
516    _checksum_cache.init_cache(d.getVar("BB_CACHEDIR"))
517    _revisions_cache.init_cache(d.getVar("BB_CACHEDIR"))
518
519    if not servercontext:
520        return
521
522    try:
523        # fetcher_init is called multiple times, so make sure we only save the
524        # revs the first time it is called.
525        if not bb.fetch2.saved_headrevs:
526            bb.fetch2.saved_headrevs = _revisions_cache.get_revs()
527    except:
528        pass
529
530    # When to drop SCM head revisions controlled by user policy
531    srcrev_policy = d.getVar('BB_SRCREV_POLICY') or "clear"
532    if srcrev_policy == "cache":
533        logger.debug("Keeping SRCREV cache due to cache policy of: %s", srcrev_policy)
534    elif srcrev_policy == "clear":
535        logger.debug("Clearing SRCREV cache due to cache policy of: %s", srcrev_policy)
536        _revisions_cache.clear_cache()
537    else:
538        raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy)
539
540
541    for m in methods:
542        if hasattr(m, "init"):
543            m.init(d)
544
545def fetcher_parse_save():
546    _checksum_cache.save_extras()
547    _revisions_cache.save_extras()
548
549def fetcher_parse_done():
550    _checksum_cache.save_merge()
551    _revisions_cache.save_merge()
552
553def fetcher_compare_revisions(d):
554    """
555    Compare the revisions in the persistent cache with the saved values from
556    when bitbake was started and return true if they have changed.
557    """
558
559    headrevs = _revisions_cache.get_revs()
560    return headrevs != bb.fetch2.saved_headrevs
561
562def mirror_from_string(data):
563    mirrors = (data or "").replace('\\n',' ').split()
564    # Split into pairs
565    if len(mirrors) % 2 != 0:
566        bb.warn('Invalid mirror data %s, should have paired members.' % data)
567    return list(zip(*[iter(mirrors)]*2))
568
569def verify_checksum(ud, d, precomputed={}, localpath=None, fatal_nochecksum=True):
570    """
571    verify the MD5 and SHA256 checksum for downloaded src
572
573    Raises a FetchError if one or both of the SRC_URI checksums do not match
574    the downloaded file, or if BB_STRICT_CHECKSUM is set and there are no
575    checksums specified.
576
577    Returns a dict of checksums that can be stored in a done stamp file and
578    passed in as precomputed parameter in a later call to avoid re-computing
579    the checksums from the file. This allows verifying the checksums of the
580    file against those in the recipe each time, rather than only after
581    downloading. See https://bugzilla.yoctoproject.org/show_bug.cgi?id=5571.
582    """
583    if ud.ignore_checksums or not ud.method.supports_checksum(ud):
584        return {}
585
586    if localpath is None:
587        localpath = ud.localpath
588
589    def compute_checksum_info(checksum_id):
590        checksum_name = getattr(ud, "%s_name" % checksum_id)
591
592        if checksum_id in precomputed:
593            checksum_data = precomputed[checksum_id]
594        else:
595            checksum_data = getattr(bb.utils, "%s_file" % checksum_id)(localpath)
596
597        checksum_expected = getattr(ud, "%s_expected" % checksum_id)
598
599        if checksum_expected == '':
600            checksum_expected = None
601
602        return {
603            "id": checksum_id,
604            "name": checksum_name,
605            "data": checksum_data,
606            "expected": checksum_expected
607        }
608
609    checksum_infos = []
610    for checksum_id in CHECKSUM_LIST:
611        checksum_infos.append(compute_checksum_info(checksum_id))
612
613    checksum_dict = {ci["id"] : ci["data"] for ci in checksum_infos}
614    checksum_event = {"%ssum" % ci["id"] : ci["data"] for ci in checksum_infos}
615
616    for ci in checksum_infos:
617        if ci["id"] in SHOWN_CHECKSUM_LIST:
618            checksum_lines = ["SRC_URI[%s] = \"%s\"" % (ci["name"], ci["data"])]
619
620    # If no checksum has been provided
621    if fatal_nochecksum and ud.method.recommends_checksum(ud) and all(ci["expected"] is None for ci in checksum_infos):
622        messages = []
623        strict = d.getVar("BB_STRICT_CHECKSUM") or "0"
624
625        # If strict checking enabled and neither sum defined, raise error
626        if strict == "1":
627            raise NoChecksumError("\n".join(checksum_lines))
628
629        bb.event.fire(MissingChecksumEvent(ud.url, **checksum_event), d)
630
631        if strict == "ignore":
632            return checksum_dict
633
634        # Log missing sums so user can more easily add them
635        messages.append("Missing checksum for '%s', consider adding at " \
636                        "least one to the recipe:" % ud.localpath)
637        messages.extend(checksum_lines)
638        logger.warning("\n".join(messages))
639
640    # We want to alert the user if a checksum is defined in the recipe but
641    # it does not match.
642    messages = []
643    messages.append("Checksum mismatch!")
644    bad_checksum = None
645
646    for ci in checksum_infos:
647        if ci["expected"] and ci["expected"] != ci["data"]:
648            messages.append("File: '%s' has %s checksum '%s' when '%s' was " \
649                            "expected" % (localpath, ci["id"], ci["data"], ci["expected"]))
650            bad_checksum = ci["data"]
651
652    if bad_checksum:
653        messages.append("If this change is expected (e.g. you have upgraded " \
654                        "to a new version without updating the checksums) " \
655                        "then you can use these lines within the recipe:")
656        messages.extend(checksum_lines)
657        messages.append("Otherwise you should retry the download and/or " \
658                        "check with upstream to determine if the file has " \
659                        "become corrupted or otherwise unexpectedly modified.")
660        raise ChecksumError("\n".join(messages), ud.url, bad_checksum)
661
662    return checksum_dict
663
664def verify_donestamp(ud, d, origud=None):
665    """
666    Check whether the done stamp file has the right checksums (if the fetch
667    method supports them). If it doesn't, delete the done stamp and force
668    a re-download.
669
670    Returns True, if the donestamp exists and is valid, False otherwise. When
671    returning False, any existing done stamps are removed.
672    """
673    if not ud.needdonestamp or (origud and not origud.needdonestamp):
674        return True
675
676    if not os.path.exists(ud.localpath):
677        # local path does not exist
678        if os.path.exists(ud.donestamp):
679            # done stamp exists, but the downloaded file does not; the done stamp
680            # must be incorrect, re-trigger the download
681            bb.utils.remove(ud.donestamp)
682        return False
683
684    if (not ud.method.supports_checksum(ud) or
685        (origud and not origud.method.supports_checksum(origud))):
686        # if done stamp exists and checksums not supported; assume the local
687        # file is current
688        return os.path.exists(ud.donestamp)
689
690    precomputed_checksums = {}
691    # Only re-use the precomputed checksums if the donestamp is newer than the
692    # file. Do not rely on the mtime of directories, though. If ud.localpath is
693    # a directory, there will probably not be any checksums anyway.
694    if os.path.exists(ud.donestamp) and (os.path.isdir(ud.localpath) or
695            os.path.getmtime(ud.localpath) < os.path.getmtime(ud.donestamp)):
696        try:
697            with open(ud.donestamp, "rb") as cachefile:
698                pickled = pickle.Unpickler(cachefile)
699                precomputed_checksums.update(pickled.load())
700        except Exception as e:
701            # Avoid the warnings on the upgrade path from emtpy done stamp
702            # files to those containing the checksums.
703            if not isinstance(e, EOFError):
704                # Ignore errors, they aren't fatal
705                logger.warning("Couldn't load checksums from donestamp %s: %s "
706                               "(msg: %s)" % (ud.donestamp, type(e).__name__,
707                                              str(e)))
708
709    try:
710        checksums = verify_checksum(ud, d, precomputed_checksums)
711        # If the cache file did not have the checksums, compute and store them
712        # as an upgrade path from the previous done stamp file format.
713        if checksums != precomputed_checksums:
714            with open(ud.donestamp, "wb") as cachefile:
715                p = pickle.Pickler(cachefile, 2)
716                p.dump(checksums)
717        return True
718    except ChecksumError as e:
719        # Checksums failed to verify, trigger re-download and remove the
720        # incorrect stamp file.
721        logger.warning("Checksum mismatch for local file %s\n"
722                       "Cleaning and trying again." % ud.localpath)
723        if os.path.exists(ud.localpath):
724            rename_bad_checksum(ud, e.checksum)
725        bb.utils.remove(ud.donestamp)
726    return False
727
728
729def update_stamp(ud, d):
730    """
731        donestamp is file stamp indicating the whole fetching is done
732        this function update the stamp after verifying the checksum
733    """
734    if not ud.needdonestamp:
735        return
736
737    if os.path.exists(ud.donestamp):
738        # Touch the done stamp file to show active use of the download
739        try:
740            os.utime(ud.donestamp, None)
741        except:
742            # Errors aren't fatal here
743            pass
744    else:
745        try:
746            checksums = verify_checksum(ud, d)
747            # Store the checksums for later re-verification against the recipe
748            with open(ud.donestamp, "wb") as cachefile:
749                p = pickle.Pickler(cachefile, 2)
750                p.dump(checksums)
751        except ChecksumError as e:
752            # Checksums failed to verify, trigger re-download and remove the
753            # incorrect stamp file.
754            logger.warning("Checksum mismatch for local file %s\n"
755                           "Cleaning and trying again." % ud.localpath)
756            if os.path.exists(ud.localpath):
757                rename_bad_checksum(ud, e.checksum)
758            bb.utils.remove(ud.donestamp)
759            raise
760
761def subprocess_setup():
762    # Python installs a SIGPIPE handler by default. This is usually not what
763    # non-Python subprocesses expect.
764    # SIGPIPE errors are known issues with gzip/bash
765    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
766
767def mark_recipe_nocache(d):
768    if d.getVar('BB_SRCREV_POLICY') != "cache":
769        d.setVar('BB_DONT_CACHE', '1')
770
771def get_autorev(d):
772    mark_recipe_nocache(d)
773    d.setVar("__BBAUTOREV_SEEN", True)
774    return "AUTOINC"
775
776def _get_srcrev(d, method_name='sortable_revision'):
777    """
778    Return the revision string, usually for use in the version string (PV) of the current package
779    Most packages usually only have one SCM so we just pass on the call.
780    In the multi SCM case, we build a value based on SRCREV_FORMAT which must
781    have been set.
782
783    The idea here is that we put the string "AUTOINC+" into return value if the revisions are not
784    incremental, other code is then responsible for turning that into an increasing value (if needed)
785
786    A method_name can be supplied to retrieve an alternatively formatted revision from a fetcher, if
787    that fetcher provides a method with the given name and the same signature as sortable_revision.
788    """
789
790    d.setVar("__BBSRCREV_SEEN", "1")
791    recursion = d.getVar("__BBINSRCREV")
792    if recursion:
793        raise FetchError("There are recursive references in fetcher variables, likely through SRC_URI")
794    d.setVar("__BBINSRCREV", True)
795
796    scms = []
797    revs = []
798    fetcher = Fetch(d.getVar('SRC_URI').split(), d)
799    urldata = fetcher.ud
800    for u in urldata:
801        if urldata[u].method.supports_srcrev():
802            scms.append(u)
803
804    if not scms:
805        d.delVar("__BBINSRCREV")
806        return "", revs
807
808
809    if len(scms) == 1 and len(urldata[scms[0]].names) == 1:
810        autoinc, rev = getattr(urldata[scms[0]].method, method_name)(urldata[scms[0]], d, urldata[scms[0]].names[0])
811        revs.append(rev)
812        if len(rev) > 10:
813            rev = rev[:10]
814        d.delVar("__BBINSRCREV")
815        if autoinc:
816            return "AUTOINC+" + rev, revs
817        return rev, revs
818
819    #
820    # Mutiple SCMs are in SRC_URI so we resort to SRCREV_FORMAT
821    #
822    format = d.getVar('SRCREV_FORMAT')
823    if not format:
824        raise FetchError("The SRCREV_FORMAT variable must be set when multiple SCMs are used.\n"\
825                         "The SCMs are:\n%s" % '\n'.join(scms))
826
827    name_to_rev = {}
828    seenautoinc = False
829    for scm in scms:
830        ud = urldata[scm]
831        for name in ud.names:
832            autoinc, rev = getattr(ud.method, method_name)(ud, d, name)
833            revs.append(rev)
834            seenautoinc = seenautoinc or autoinc
835            if len(rev) > 10:
836                rev = rev[:10]
837            name_to_rev[name] = rev
838    # Replace names by revisions in the SRCREV_FORMAT string. The approach used
839    # here can handle names being prefixes of other names and names appearing
840    # as substrings in revisions (in which case the name should not be
841    # expanded). The '|' regular expression operator tries matches from left to
842    # right, so we need to sort the names with the longest ones first.
843    names_descending_len = sorted(name_to_rev, key=len, reverse=True)
844    name_to_rev_re = "|".join(re.escape(name) for name in names_descending_len)
845    format = re.sub(name_to_rev_re, lambda match: name_to_rev[match.group(0)], format)
846
847    if seenautoinc:
848        format = "AUTOINC+" + format
849
850    d.delVar("__BBINSRCREV")
851    return format, revs
852
853def get_hashvalue(d, method_name='sortable_revision'):
854    pkgv, revs = _get_srcrev(d, method_name=method_name)
855    return " ".join(revs)
856
857def get_pkgv_string(d, method_name='sortable_revision'):
858    pkgv, revs = _get_srcrev(d, method_name=method_name)
859    return pkgv
860
861def get_srcrev(d, method_name='sortable_revision'):
862    pkgv, revs = _get_srcrev(d, method_name=method_name)
863    if not pkgv:
864        raise FetchError("SRCREV was used yet no valid SCM was found in SRC_URI")
865    return pkgv
866
867def localpath(url, d):
868    fetcher = bb.fetch2.Fetch([url], d)
869    return fetcher.localpath(url)
870
871# Need to export PATH as binary could be in metadata paths
872# rather than host provided
873# Also include some other variables.
874FETCH_EXPORT_VARS = ['HOME', 'PATH',
875                     'HTTP_PROXY', 'http_proxy',
876                     'HTTPS_PROXY', 'https_proxy',
877                     'FTP_PROXY', 'ftp_proxy',
878                     'FTPS_PROXY', 'ftps_proxy',
879                     'NO_PROXY', 'no_proxy',
880                     'ALL_PROXY', 'all_proxy',
881                     'GIT_PROXY_COMMAND',
882                     'GIT_SSH',
883                     'GIT_SSH_COMMAND',
884                     'GIT_SSL_CAINFO',
885                     'GIT_SMART_HTTP',
886                     'SSH_AUTH_SOCK', 'SSH_AGENT_PID',
887                     'SOCKS5_USER', 'SOCKS5_PASSWD',
888                     'DBUS_SESSION_BUS_ADDRESS',
889                     'P4CONFIG',
890                     'SSL_CERT_FILE',
891                     'NODE_EXTRA_CA_CERTS',
892                     'AWS_PROFILE',
893                     'AWS_ACCESS_KEY_ID',
894                     'AWS_SECRET_ACCESS_KEY',
895                     'AWS_ROLE_ARN',
896                     'AWS_WEB_IDENTITY_TOKEN_FILE',
897                     'AWS_DEFAULT_REGION',
898                     'AWS_SESSION_TOKEN',
899                     'GIT_CACHE_PATH',
900                     'REMOTE_CONTAINERS_IPC',
901                     'GITHUB_TOKEN',
902                     'SSL_CERT_DIR']
903
904def get_fetcher_environment(d):
905    newenv = {}
906    origenv = d.getVar("BB_ORIGENV")
907    for name in bb.fetch2.FETCH_EXPORT_VARS:
908        value = d.getVar(name)
909        if not value and origenv:
910            value = origenv.getVar(name)
911        if value:
912            newenv[name] = value
913    return newenv
914
915def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None):
916    """
917    Run cmd returning the command output
918    Raise an error if interrupted or cmd fails
919    Optionally echo command output to stdout
920    Optionally remove the files/directories listed in cleanup upon failure
921    """
922
923    exportvars = FETCH_EXPORT_VARS
924
925    if not cleanup:
926        cleanup = []
927
928    # If PATH contains WORKDIR which contains PV-PR which contains SRCPV we
929    # can end up in circular recursion here so give the option of breaking it
930    # in a data store copy.
931    try:
932        d.getVar("PV")
933        d.getVar("PR")
934    except bb.data_smart.ExpansionError:
935        d = bb.data.createCopy(d)
936        d.setVar("PV", "fetcheravoidrecurse")
937        d.setVar("PR", "fetcheravoidrecurse")
938
939    origenv = d.getVar("BB_ORIGENV", False)
940    for var in exportvars:
941        val = d.getVar(var) or (origenv and origenv.getVar(var))
942        if val:
943            cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd)
944
945    # Disable pseudo as it may affect ssh, potentially causing it to hang.
946    cmd = 'export PSEUDO_DISABLED=1; ' + cmd
947
948    if workdir:
949        logger.debug("Running '%s' in %s" % (cmd, workdir))
950    else:
951        logger.debug("Running %s", cmd)
952
953    success = False
954    error_message = ""
955
956    try:
957        (output, errors) = bb.process.run(cmd, log=log, shell=True, stderr=subprocess.PIPE, cwd=workdir)
958        success = True
959    except bb.process.NotFoundError as e:
960        error_message = "Fetch command %s not found" % (e.command)
961    except bb.process.ExecutionError as e:
962        if e.stdout:
963            output = "output:\n%s\n%s" % (e.stdout, e.stderr)
964        elif e.stderr:
965            output = "output:\n%s" % e.stderr
966        else:
967            if log:
968                output = "see logfile for output"
969            else:
970                output = "no output"
971        error_message = "Fetch command %s failed with exit code %s, %s" % (e.command, e.exitcode, output)
972    except bb.process.CmdError as e:
973        error_message = "Fetch command %s could not be run:\n%s" % (e.command, e.msg)
974    if not success:
975        for f in cleanup:
976            try:
977                bb.utils.remove(f, True)
978            except OSError:
979                pass
980
981        raise FetchError(error_message)
982
983    return output
984
985def check_network_access(d, info, url):
986    """
987    log remote network access, and error if BB_NO_NETWORK is set or the given
988    URI is untrusted
989    """
990    if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")):
991        raise NetworkAccess(url, info)
992    elif not trusted_network(d, url):
993        raise UntrustedUrl(url, info)
994    else:
995        logger.debug("Fetcher accessed the network with the command %s" % info)
996
997def build_mirroruris(origud, mirrors, ld):
998    uris = []
999    uds = []
1000
1001    replacements = {}
1002    replacements["TYPE"] = origud.type
1003    replacements["HOST"] = origud.host
1004    replacements["PATH"] = origud.path
1005    replacements["BASENAME"] = origud.path.split("/")[-1]
1006    replacements["MIRRORNAME"] = origud.host.replace(':','.') + origud.path.replace('/', '.').replace('*', '.')
1007
1008    def adduri(ud, uris, uds, mirrors, tarballs):
1009        for line in mirrors:
1010            try:
1011                (find, replace) = line
1012            except ValueError:
1013                continue
1014
1015            for tarball in tarballs:
1016                newuri = uri_replace(ud, find, replace, replacements, ld, tarball)
1017                if not newuri or newuri in uris or newuri == origud.url:
1018                    continue
1019
1020                if not trusted_network(ld, newuri):
1021                    logger.debug("Mirror %s not in the list of trusted networks, skipping" %  (newuri))
1022                    continue
1023
1024                # Create a local copy of the mirrors minus the current line
1025                # this will prevent us from recursively processing the same line
1026                # as well as indirect recursion A -> B -> C -> A
1027                localmirrors = list(mirrors)
1028                localmirrors.remove(line)
1029
1030                try:
1031                    newud = FetchData(newuri, ld)
1032                    newud.ignore_checksums = True
1033                    newud.setup_localpath(ld)
1034                except bb.fetch2.BBFetchException as e:
1035                    logger.debug("Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
1036                    logger.debug(str(e))
1037                    try:
1038                        # setup_localpath of file:// urls may fail, we should still see
1039                        # if mirrors of the url exist
1040                        adduri(newud, uris, uds, localmirrors, tarballs)
1041                    except UnboundLocalError:
1042                        pass
1043                    continue
1044                uris.append(newuri)
1045                uds.append(newud)
1046
1047                adduri(newud, uris, uds, localmirrors, tarballs)
1048
1049    adduri(origud, uris, uds, mirrors, origud.mirrortarballs or [None])
1050
1051    return uris, uds
1052
1053def rename_bad_checksum(ud, suffix):
1054    """
1055    Renames files to have suffix from parameter
1056    """
1057
1058    if ud.localpath is None:
1059        return
1060
1061    new_localpath = "%s_bad-checksum_%s" % (ud.localpath, suffix)
1062    bb.warn("Renaming %s to %s" % (ud.localpath, new_localpath))
1063    if not bb.utils.movefile(ud.localpath, new_localpath):
1064        bb.warn("Renaming %s to %s failed, grep movefile in log.do_fetch to see why" % (ud.localpath, new_localpath))
1065
1066
1067def try_mirror_url(fetch, origud, ud, ld, check = False):
1068    # Return of None or a value means we're finished
1069    # False means try another url
1070
1071    if ud.lockfile and ud.lockfile != origud.lockfile:
1072        lf = bb.utils.lockfile(ud.lockfile)
1073
1074    try:
1075        if check:
1076            found = ud.method.checkstatus(fetch, ud, ld)
1077            if found:
1078                return found
1079            return False
1080
1081        if not verify_donestamp(ud, ld, origud) or ud.method.need_update(ud, ld):
1082            ud.method.download(ud, ld)
1083            if hasattr(ud.method,"build_mirror_data"):
1084                ud.method.build_mirror_data(ud, ld)
1085
1086        if not ud.localpath or not os.path.exists(ud.localpath):
1087            return False
1088
1089        if ud.localpath == origud.localpath:
1090            return ud.localpath
1091
1092        # We may be obtaining a mirror tarball which needs further processing by the real fetcher
1093        # If that tarball is a local file:// we need to provide a symlink to it
1094        dldir = ld.getVar("DL_DIR")
1095
1096        if origud.mirrortarballs and os.path.basename(ud.localpath) in origud.mirrortarballs and os.path.basename(ud.localpath) != os.path.basename(origud.localpath):
1097            # Create donestamp in old format to avoid triggering a re-download
1098            if ud.donestamp:
1099                bb.utils.mkdirhier(os.path.dirname(ud.donestamp))
1100                open(ud.donestamp, 'w').close()
1101            dest = os.path.join(dldir, os.path.basename(ud.localpath))
1102            if not os.path.exists(dest):
1103                # In case this is executing without any file locks held (as is
1104                # the case for file:// URLs), two tasks may end up here at the
1105                # same time, in which case we do not want the second task to
1106                # fail when the link has already been created by the first task.
1107                try:
1108                    os.symlink(ud.localpath, dest)
1109                except FileExistsError:
1110                    pass
1111            if not verify_donestamp(origud, ld) or origud.method.need_update(origud, ld):
1112                origud.method.download(origud, ld)
1113                if hasattr(origud.method, "build_mirror_data"):
1114                    origud.method.build_mirror_data(origud, ld)
1115            return origud.localpath
1116        # Otherwise the result is a local file:// and we symlink to it
1117        ensure_symlink(ud.localpath, origud.localpath)
1118        update_stamp(origud, ld)
1119        return ud.localpath
1120
1121    except bb.fetch2.NetworkAccess:
1122        raise
1123
1124    except IOError as e:
1125        if e.errno in [errno.ESTALE]:
1126            logger.warning("Stale Error Observed %s." % ud.url)
1127            return False
1128        raise
1129
1130    except bb.fetch2.BBFetchException as e:
1131        if isinstance(e, ChecksumError):
1132            logger.warning("Mirror checksum failure for url %s (original url: %s)\nCleaning and trying again." % (ud.url, origud.url))
1133            logger.warning(str(e))
1134            if os.path.exists(ud.localpath):
1135                rename_bad_checksum(ud, e.checksum)
1136        elif isinstance(e, NoChecksumError):
1137            raise
1138        else:
1139            logger.debug("Mirror fetch failure for url %s (original url: %s)" % (ud.url, origud.url))
1140            logger.debug(str(e))
1141        try:
1142            if ud.method.cleanup_upon_failure():
1143                ud.method.clean(ud, ld)
1144        except UnboundLocalError:
1145            pass
1146        return False
1147    finally:
1148        if ud.lockfile and ud.lockfile != origud.lockfile:
1149            bb.utils.unlockfile(lf)
1150
1151
1152def ensure_symlink(target, link_name):
1153    if not os.path.exists(link_name):
1154        dirname = os.path.dirname(link_name)
1155        bb.utils.mkdirhier(dirname)
1156        if os.path.islink(link_name):
1157            # Broken symbolic link
1158            os.unlink(link_name)
1159
1160        # In case this is executing without any file locks held (as is
1161        # the case for file:// URLs), two tasks may end up here at the
1162        # same time, in which case we do not want the second task to
1163        # fail when the link has already been created by the first task.
1164        try:
1165            os.symlink(target, link_name)
1166        except FileExistsError:
1167            pass
1168
1169
1170def try_mirrors(fetch, d, origud, mirrors, check = False):
1171    """
1172    Try to use a mirrored version of the sources.
1173    This method will be automatically called before the fetchers go.
1174
1175    d Is a bb.data instance
1176    uri is the original uri we're trying to download
1177    mirrors is the list of mirrors we're going to try
1178    """
1179    ld = d.createCopy()
1180
1181    uris, uds = build_mirroruris(origud, mirrors, ld)
1182
1183    for index, uri in enumerate(uris):
1184        ret = try_mirror_url(fetch, origud, uds[index], ld, check)
1185        if ret:
1186            return ret
1187    return None
1188
1189def trusted_network(d, url):
1190    """
1191    Use a trusted url during download if networking is enabled and
1192    BB_ALLOWED_NETWORKS is set globally or for a specific recipe.
1193    Note: modifies SRC_URI & mirrors.
1194    """
1195    if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")):
1196        return True
1197
1198    pkgname = d.getVar('PN')
1199    trusted_hosts = None
1200    if pkgname:
1201        trusted_hosts = d.getVarFlag('BB_ALLOWED_NETWORKS', pkgname, False)
1202
1203    if not trusted_hosts:
1204        trusted_hosts = d.getVar('BB_ALLOWED_NETWORKS')
1205
1206    # Not enabled.
1207    if not trusted_hosts:
1208        return True
1209
1210    scheme, network, path, user, passwd, param = decodeurl(url)
1211
1212    if not network:
1213        return True
1214
1215    network = network.split(':')[0]
1216    network = network.lower()
1217
1218    for host in trusted_hosts.split(" "):
1219        host = host.lower()
1220        if host.startswith("*.") and ("." + network).endswith(host[1:]):
1221            return True
1222        if host == network:
1223            return True
1224
1225    return False
1226
1227def srcrev_internal_helper(ud, d, name):
1228    """
1229    Return:
1230        a) a source revision if specified
1231        b) latest revision if SRCREV="AUTOINC"
1232        c) None if not specified
1233    """
1234
1235    srcrev = None
1236    pn = d.getVar("PN")
1237    attempts = []
1238    if name != '' and pn:
1239        attempts.append("SRCREV_%s:pn-%s" % (name, pn))
1240    if name != '':
1241        attempts.append("SRCREV_%s" % name)
1242    if pn:
1243        attempts.append("SRCREV:pn-%s" % pn)
1244    attempts.append("SRCREV")
1245
1246    for a in attempts:
1247        srcrev = d.getVar(a)
1248        if srcrev and srcrev != "INVALID":
1249            break
1250
1251    if 'rev' in ud.parm and 'tag' in ud.parm:
1252        raise FetchError("Please specify a ;rev= parameter or a ;tag= parameter in the url %s but not both." % (ud.url))
1253
1254    if 'rev' in ud.parm or 'tag' in ud.parm:
1255        if 'rev' in ud.parm:
1256            parmrev = ud.parm['rev']
1257        else:
1258            parmrev = ud.parm['tag']
1259        if srcrev == "INVALID" or not srcrev:
1260            return parmrev
1261        if srcrev != parmrev:
1262            raise FetchError("Conflicting revisions (%s from SRCREV and %s from the url) found, please specify one valid value" % (srcrev, parmrev))
1263        return parmrev
1264
1265    if srcrev == "INVALID" or not srcrev:
1266        raise FetchError("Please set a valid SRCREV for url %s (possible key names are %s, or use a ;rev=X URL parameter)" % (str(attempts), ud.url), ud.url)
1267    if srcrev == "AUTOINC":
1268        d.setVar("__BBAUTOREV_ACTED_UPON", True)
1269        srcrev = ud.method.latest_revision(ud, d, name)
1270
1271    return srcrev
1272
1273def get_checksum_file_list(d):
1274    """ Get a list of files checksum in SRC_URI
1275
1276    Returns the resolved local paths of all local file entries in
1277    SRC_URI as a space-separated string
1278    """
1279    fetch = Fetch([], d, cache = False, localonly = True)
1280    filelist = []
1281    for u in fetch.urls:
1282        ud = fetch.ud[u]
1283        if ud and isinstance(ud.method, local.Local):
1284            found = False
1285            paths = ud.method.localfile_searchpaths(ud, d)
1286            for f in paths:
1287                pth = ud.path
1288                if os.path.exists(f):
1289                    found = True
1290                filelist.append(f + ":" + str(os.path.exists(f)))
1291            if not found:
1292                bb.fatal(("Unable to get checksum for %s SRC_URI entry %s: file could not be found"
1293                            "\nThe following paths were searched:"
1294                            "\n%s") % (d.getVar('PN'), os.path.basename(f), '\n'.join(paths)))
1295
1296    return " ".join(filelist)
1297
1298def get_file_checksums(filelist, pn, localdirsexclude):
1299    """Get a list of the checksums for a list of local files
1300
1301    Returns the checksums for a list of local files, caching the results as
1302    it proceeds
1303
1304    """
1305    return _checksum_cache.get_checksums(filelist, pn, localdirsexclude)
1306
1307
1308class FetchData(object):
1309    """
1310    A class which represents the fetcher state for a given URI.
1311    """
1312    def __init__(self, url, d, localonly = False):
1313        # localpath is the location of a downloaded result. If not set, the file is local.
1314        self.donestamp = None
1315        self.needdonestamp = True
1316        self.localfile = ""
1317        self.localpath = None
1318        self.lockfile = None
1319        self.mirrortarballs = []
1320        self.basename = None
1321        self.basepath = None
1322        (self.type, self.host, self.path, self.user, self.pswd, self.parm) = decodeurl(d.expand(url))
1323        self.date = self.getSRCDate(d)
1324        self.url = url
1325        if not self.user and "user" in self.parm:
1326            self.user = self.parm["user"]
1327        if not self.pswd and "pswd" in self.parm:
1328            self.pswd = self.parm["pswd"]
1329        self.setup = False
1330
1331        def configure_checksum(checksum_id):
1332            checksum_plain_name = "%ssum" % checksum_id
1333            if "name" in self.parm:
1334                checksum_name = "%s.%ssum" % (self.parm["name"], checksum_id)
1335            else:
1336                checksum_name = checksum_plain_name
1337
1338            if checksum_name in self.parm:
1339                checksum_expected = self.parm[checksum_name]
1340            elif checksum_plain_name in self.parm:
1341                checksum_expected = self.parm[checksum_plain_name]
1342                checksum_name = checksum_plain_name
1343            elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3", "az", "crate", "gs", "gomod", "npm"]:
1344                checksum_expected = None
1345            else:
1346                checksum_expected = d.getVarFlag("SRC_URI", checksum_name)
1347
1348            setattr(self, "%s_name" % checksum_id, checksum_name)
1349            setattr(self, "%s_expected" % checksum_id, checksum_expected)
1350
1351        self.names = self.parm.get("name",'default').split(',')
1352
1353        self.method = None
1354        for m in methods:
1355            if m.supports(self, d):
1356                self.method = m
1357                break
1358
1359        if not self.method:
1360            raise NoMethodError(url)
1361
1362        if localonly and not isinstance(self.method, local.Local):
1363            raise NonLocalMethod()
1364
1365        if self.parm.get("proto", None) and "protocol" not in self.parm:
1366            logger.warning('Consider updating %s recipe to use "protocol" not "proto" in SRC_URI.', d.getVar('PN'))
1367            self.parm["protocol"] = self.parm.get("proto", None)
1368
1369        if hasattr(self.method, "urldata_init"):
1370            self.method.urldata_init(self, d)
1371
1372        for checksum_id in CHECKSUM_LIST:
1373            configure_checksum(checksum_id)
1374
1375        self.ignore_checksums = False
1376
1377        if "localpath" in self.parm:
1378            # if user sets localpath for file, use it instead.
1379            self.localpath = self.parm["localpath"]
1380            self.basename = os.path.basename(self.localpath)
1381        elif self.localfile:
1382            self.localpath = self.method.localpath(self, d)
1383
1384        dldir = d.getVar("DL_DIR")
1385
1386        if not self.needdonestamp:
1387            return
1388
1389        # Note: .done and .lock files should always be in DL_DIR whereas localpath may not be.
1390        if self.localpath and self.localpath.startswith(dldir):
1391            basepath = self.localpath
1392        elif self.localpath:
1393            basepath = dldir + os.sep + os.path.basename(self.localpath)
1394        elif self.basepath or self.basename:
1395            basepath = dldir + os.sep + (self.basepath or self.basename)
1396        else:
1397            bb.fatal("Can't determine lock path for url %s" % url)
1398
1399        self.donestamp = basepath + '.done'
1400        self.lockfile = basepath + '.lock'
1401
1402    def setup_revisions(self, d):
1403        self.revisions = {}
1404        for name in self.names:
1405            self.revisions[name] = srcrev_internal_helper(self, d, name)
1406
1407        # add compatibility code for non name specified case
1408        if len(self.names) == 1:
1409            self.revision = self.revisions[self.names[0]]
1410
1411    def setup_localpath(self, d):
1412        if not self.localpath:
1413            self.localpath = self.method.localpath(self, d)
1414
1415    def getSRCDate(self, d):
1416        """
1417        Return the SRC Date for the component
1418
1419        d the bb.data module
1420        """
1421        if "srcdate" in self.parm:
1422            return self.parm['srcdate']
1423
1424        pn = d.getVar("PN")
1425
1426        if pn:
1427            return d.getVar("SRCDATE_%s" % pn) or d.getVar("SRCDATE") or d.getVar("DATE")
1428
1429        return d.getVar("SRCDATE") or d.getVar("DATE")
1430
1431class FetchMethod(object):
1432    """Base class for 'fetch'ing data"""
1433
1434    def __init__(self, urls=None):
1435        self.urls = []
1436
1437    def supports(self, urldata, d):
1438        """
1439        Check to see if this fetch class supports a given url.
1440        """
1441        return 0
1442
1443    def localpath(self, urldata, d):
1444        """
1445        Return the local filename of a given url assuming a successful fetch.
1446        Can also setup variables in urldata for use in go (saving code duplication
1447        and duplicate code execution)
1448        """
1449        return os.path.join(d.getVar("DL_DIR"), urldata.localfile)
1450
1451    def supports_checksum(self, urldata):
1452        """
1453        Is localpath something that can be represented by a checksum?
1454        """
1455
1456        # We cannot compute checksums for None
1457        if urldata.localpath is None:
1458            return False
1459        # We cannot compute checksums for directories
1460        if os.path.isdir(urldata.localpath):
1461            return False
1462        return True
1463
1464    def recommends_checksum(self, urldata):
1465        """
1466        Is the backend on where checksumming is recommended (should warnings
1467        be displayed if there is no checksum)?
1468        """
1469        return False
1470
1471    def cleanup_upon_failure(self):
1472        """
1473        When a fetch fails, should clean() be called?
1474        """
1475        return True
1476
1477    def verify_donestamp(self, ud, d):
1478        """
1479        Verify the donestamp file
1480        """
1481        return verify_donestamp(ud, d)
1482
1483    def update_donestamp(self, ud, d):
1484        """
1485        Update the donestamp file
1486        """
1487        update_stamp(ud, d)
1488
1489    def _strip_leading_slashes(self, relpath):
1490        """
1491        Remove leading slash as os.path.join can't cope
1492        """
1493        while os.path.isabs(relpath):
1494            relpath = relpath[1:]
1495        return relpath
1496
1497    def setUrls(self, urls):
1498        self.__urls = urls
1499
1500    def getUrls(self):
1501        return self.__urls
1502
1503    urls = property(getUrls, setUrls, None, "Urls property")
1504
1505    def need_update(self, ud, d):
1506        """
1507        Force a fetch, even if localpath exists?
1508        """
1509        if os.path.exists(ud.localpath):
1510            return False
1511        return True
1512
1513    def supports_srcrev(self):
1514        """
1515        The fetcher supports auto source revisions (SRCREV)
1516        """
1517        return False
1518
1519    def download(self, urldata, d):
1520        """
1521        Fetch urls
1522        Assumes localpath was called first
1523        """
1524        raise NoMethodError(urldata.url)
1525
1526    def unpack(self, urldata, rootdir, data):
1527        iterate = False
1528        file = urldata.localpath
1529
1530        try:
1531            unpack = bb.utils.to_boolean(urldata.parm.get('unpack'), True)
1532        except ValueError as exc:
1533            bb.fatal("Invalid value for 'unpack' parameter for %s: %s" %
1534                     (file, urldata.parm.get('unpack')))
1535
1536        base, ext = os.path.splitext(file)
1537        if ext in ['.gz', '.bz2', '.Z', '.xz', '.lz', '.zst']:
1538            efile = os.path.join(rootdir, os.path.basename(base))
1539        else:
1540            efile = file
1541        cmd = None
1542
1543        if unpack:
1544            tar_cmd = 'tar --extract --no-same-owner'
1545            if 'striplevel' in urldata.parm:
1546                tar_cmd += ' --strip-components=%s' %  urldata.parm['striplevel']
1547            if file.endswith('.tar'):
1548                cmd = '%s -f %s' % (tar_cmd, file)
1549            elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
1550                cmd = '%s -z -f %s' % (tar_cmd, file)
1551            elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
1552                cmd = 'bzip2 -dc %s | %s -f -' % (file, tar_cmd)
1553            elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
1554                cmd = 'gzip -dc %s > %s' % (file, efile)
1555            elif file.endswith('.bz2'):
1556                cmd = 'bzip2 -dc %s > %s' % (file, efile)
1557            elif file.endswith('.txz') or file.endswith('.tar.xz'):
1558                cmd = 'xz -dc %s | %s -f -' % (file, tar_cmd)
1559            elif file.endswith('.xz'):
1560                cmd = 'xz -dc %s > %s' % (file, efile)
1561            elif file.endswith('.tar.lz'):
1562                cmd = 'lzip -dc %s | %s -f -' % (file, tar_cmd)
1563            elif file.endswith('.lz'):
1564                cmd = 'lzip -dc %s > %s' % (file, efile)
1565            elif file.endswith('.tar.7z'):
1566                cmd = '7z x -so %s | %s -f -' % (file, tar_cmd)
1567            elif file.endswith('.7z'):
1568                cmd = '7za x -y %s 1>/dev/null' % file
1569            elif file.endswith('.tzst') or file.endswith('.tar.zst'):
1570                cmd = 'zstd --decompress --stdout %s | %s -f -' % (file, tar_cmd)
1571            elif file.endswith('.zst'):
1572                cmd = 'zstd --decompress --stdout %s > %s' % (file, efile)
1573            elif file.endswith('.zip') or file.endswith('.jar'):
1574                try:
1575                    dos = bb.utils.to_boolean(urldata.parm.get('dos'), False)
1576                except ValueError as exc:
1577                    bb.fatal("Invalid value for 'dos' parameter for %s: %s" %
1578                             (file, urldata.parm.get('dos')))
1579                cmd = 'unzip -q -o'
1580                if dos:
1581                    cmd = '%s -a' % cmd
1582                cmd = "%s '%s'" % (cmd, file)
1583            elif file.endswith('.rpm') or file.endswith('.srpm'):
1584                if 'extract' in urldata.parm:
1585                    unpack_file = urldata.parm.get('extract')
1586                    cmd = 'rpm2cpio.sh %s | cpio -id %s' % (file, unpack_file)
1587                    iterate = True
1588                    iterate_file = unpack_file
1589                else:
1590                    cmd = 'rpm2cpio.sh %s | cpio -id' % (file)
1591            elif file.endswith('.deb') or file.endswith('.ipk'):
1592                output = subprocess.check_output(['ar', '-t', file], preexec_fn=subprocess_setup)
1593                datafile = None
1594                if output:
1595                    for line in output.decode().splitlines():
1596                        if line.startswith('data.tar.'):
1597                            datafile = line
1598                            break
1599                    else:
1600                        raise UnpackError("Unable to unpack deb/ipk package - does not contain data.tar.* file", urldata.url)
1601                else:
1602                    raise UnpackError("Unable to unpack deb/ipk package - could not list contents", urldata.url)
1603                cmd = 'ar x %s %s && %s -p -f %s && rm %s' % (file, datafile, tar_cmd, datafile, datafile)
1604
1605        # If 'subdir' param exists, create a dir and use it as destination for unpack cmd
1606        if 'subdir' in urldata.parm:
1607            subdir = urldata.parm.get('subdir')
1608            if os.path.isabs(subdir):
1609                if not os.path.realpath(subdir).startswith(os.path.realpath(rootdir)):
1610                    raise UnpackError("subdir argument isn't a subdirectory of unpack root %s" % rootdir, urldata.url)
1611                unpackdir = subdir
1612            else:
1613                unpackdir = os.path.join(rootdir, subdir)
1614            bb.utils.mkdirhier(unpackdir)
1615        else:
1616            unpackdir = rootdir
1617
1618        if not unpack or not cmd:
1619            urldata.unpack_tracer.unpack("file-copy", unpackdir)
1620            # If file == dest, then avoid any copies, as we already put the file into dest!
1621            dest = os.path.join(unpackdir, os.path.basename(file))
1622            if file != dest and not (os.path.exists(dest) and os.path.samefile(file, dest)):
1623                destdir = '.'
1624                # For file:// entries all intermediate dirs in path must be created at destination
1625                if urldata.type == "file":
1626                    # Trailing '/' does a copying to wrong place
1627                    urlpath = urldata.path.rstrip('/')
1628                    # Want files places relative to cwd so no leading '/'
1629                    urlpath = urlpath.lstrip('/')
1630                    if urlpath.find("/") != -1:
1631                        destdir = urlpath.rsplit("/", 1)[0] + '/'
1632                        bb.utils.mkdirhier("%s/%s" % (unpackdir, destdir))
1633                cmd = 'cp --force --preserve=timestamps --no-dereference --recursive -H "%s" "%s"' % (file, destdir)
1634        else:
1635            urldata.unpack_tracer.unpack("archive-extract", unpackdir)
1636
1637        if not cmd:
1638            return
1639
1640        path = data.getVar('PATH')
1641        if path:
1642            cmd = "PATH=\"%s\" %s" % (path, cmd)
1643        bb.note("Unpacking %s to %s/" % (file, unpackdir))
1644        ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True, cwd=unpackdir)
1645
1646        if ret != 0:
1647            raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), urldata.url)
1648
1649        if iterate is True:
1650            iterate_urldata = urldata
1651            iterate_urldata.localpath = "%s/%s" % (rootdir, iterate_file)
1652            self.unpack(urldata, rootdir, data)
1653
1654        return
1655
1656    def clean(self, urldata, d):
1657        """
1658        Clean any existing full or partial download
1659        """
1660        bb.utils.remove(urldata.localpath)
1661
1662    def try_premirror(self, urldata, d):
1663        """
1664        Should premirrors be used?
1665        """
1666        return True
1667
1668    def try_mirrors(self, fetch, urldata, d, mirrors, check=False):
1669        """
1670        Try to use a mirror
1671        """
1672        return bool(try_mirrors(fetch, d, urldata, mirrors, check))
1673
1674    def checkstatus(self, fetch, urldata, d):
1675        """
1676        Check the status of a URL
1677        Assumes localpath was called first
1678        """
1679        logger.info("URL %s could not be checked for status since no method exists.", urldata.url)
1680        return True
1681
1682    def latest_revision(self, ud, d, name):
1683        """
1684        Look in the cache for the latest revision, if not present ask the SCM.
1685        """
1686        if not hasattr(self, "_latest_revision"):
1687            raise ParameterError("The fetcher for this URL does not support _latest_revision", ud.url)
1688
1689        key = self.generate_revision_key(ud, d, name)
1690
1691        rev = _revisions_cache.get_rev(key)
1692        if rev is None:
1693            rev = self._latest_revision(ud, d, name)
1694            _revisions_cache.set_rev(key, rev)
1695        return rev
1696
1697    def sortable_revision(self, ud, d, name):
1698        latest_rev = self._build_revision(ud, d, name)
1699        return True, str(latest_rev)
1700
1701    def generate_revision_key(self, ud, d, name):
1702        return self._revision_key(ud, d, name)
1703
1704    def latest_versionstring(self, ud, d):
1705        """
1706        Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
1707        by searching through the tags output of ls-remote, comparing
1708        versions and returning the highest match as a (version, revision) pair.
1709        """
1710        return ('', '')
1711
1712    def done(self, ud, d):
1713        """
1714        Is the download done ?
1715        """
1716        if os.path.exists(ud.localpath):
1717            return True
1718        return False
1719
1720    def implicit_urldata(self, ud, d):
1721        """
1722        Get a list of FetchData objects for any implicit URLs that will also
1723        be downloaded when we fetch the given URL.
1724        """
1725        return []
1726
1727
1728class DummyUnpackTracer(object):
1729    """
1730    Abstract API definition for a class that traces unpacked source files back
1731    to their respective upstream SRC_URI entries, for software composition
1732    analysis, license compliance and detailed SBOM generation purposes.
1733    User may load their own unpack tracer class (instead of the dummy
1734    one) by setting the BB_UNPACK_TRACER_CLASS config parameter.
1735    """
1736    def start(self, unpackdir, urldata_dict, d):
1737        """
1738        Start tracing the core Fetch.unpack process, using an index to map
1739        unpacked files to each SRC_URI entry.
1740        This method is called by Fetch.unpack and it may receive nested calls by
1741        gitsm and npmsw fetchers, that expand SRC_URI entries by adding implicit
1742        URLs and by recursively calling Fetch.unpack from new (nested) Fetch
1743        instances.
1744        """
1745        return
1746    def start_url(self, url):
1747        """Start tracing url unpack process.
1748        This method is called by Fetch.unpack before the fetcher-specific unpack
1749        method starts, and it may receive nested calls by gitsm and npmsw
1750        fetchers.
1751        """
1752        return
1753    def unpack(self, unpack_type, destdir):
1754        """
1755        Set unpack_type and destdir for current url.
1756        This method is called by the fetcher-specific unpack method after url
1757        tracing started.
1758        """
1759        return
1760    def finish_url(self, url):
1761        """Finish tracing url unpack process and update the file index.
1762        This method is called by Fetch.unpack after the fetcher-specific unpack
1763        method finished its job, and it may receive nested calls by gitsm
1764        and npmsw fetchers.
1765        """
1766        return
1767    def complete(self):
1768        """
1769        Finish tracing the Fetch.unpack process, and check if all nested
1770        Fecth.unpack calls (if any) have been completed; if so, save collected
1771        metadata.
1772        """
1773        return
1774
1775
1776class Fetch(object):
1777    def __init__(self, urls, d, cache = True, localonly = False, connection_cache = None):
1778        if localonly and cache:
1779            raise Exception("bb.fetch2.Fetch.__init__: cannot set cache and localonly at same time")
1780
1781        if not urls:
1782            urls = d.getVar("SRC_URI").split()
1783        self.urls = urls
1784        self.d = d
1785        self.ud = {}
1786        self.connection_cache = connection_cache
1787
1788        fn = d.getVar('FILE')
1789        mc = d.getVar('__BBMULTICONFIG') or ""
1790        key = None
1791        if cache and fn:
1792            key = mc + fn + str(id(d))
1793        if key in urldata_cache:
1794            self.ud = urldata_cache[key]
1795
1796        # the unpack_tracer object needs to be made available to possible nested
1797        # Fetch instances (when those are created by gitsm and npmsw fetchers)
1798        # so we set it as a global variable
1799        global unpack_tracer
1800        try:
1801            unpack_tracer
1802        except NameError:
1803            class_path = d.getVar("BB_UNPACK_TRACER_CLASS")
1804            if class_path:
1805                # use user-defined unpack tracer class
1806                import importlib
1807                module_name, _, class_name = class_path.rpartition(".")
1808                module = importlib.import_module(module_name)
1809                class_ = getattr(module, class_name)
1810                unpack_tracer = class_()
1811            else:
1812                # fall back to the dummy/abstract class
1813                unpack_tracer = DummyUnpackTracer()
1814
1815        for url in urls:
1816            if url not in self.ud:
1817                try:
1818                    self.ud[url] = FetchData(url, d, localonly)
1819                    self.ud[url].unpack_tracer = unpack_tracer
1820                except NonLocalMethod:
1821                    if localonly:
1822                        self.ud[url] = None
1823                        pass
1824
1825        if key:
1826            urldata_cache[key] = self.ud
1827
1828    def localpath(self, url):
1829        if url not in self.urls:
1830            self.ud[url] = FetchData(url, self.d)
1831
1832        self.ud[url].setup_localpath(self.d)
1833        return self.ud[url].localpath
1834
1835    def localpaths(self):
1836        """
1837        Return a list of the local filenames, assuming successful fetch
1838        """
1839        local = []
1840
1841        for u in self.urls:
1842            ud = self.ud[u]
1843            ud.setup_localpath(self.d)
1844            local.append(ud.localpath)
1845
1846        return local
1847
1848    def download(self, urls=None):
1849        """
1850        Fetch all urls
1851        """
1852        if not urls:
1853            urls = self.urls
1854
1855        network = self.d.getVar("BB_NO_NETWORK")
1856        premirroronly = bb.utils.to_boolean(self.d.getVar("BB_FETCH_PREMIRRORONLY"))
1857
1858        checksum_missing_messages = []
1859        for u in urls:
1860            ud = self.ud[u]
1861            ud.setup_localpath(self.d)
1862            m = ud.method
1863            done = False
1864
1865            if ud.lockfile:
1866                lf = bb.utils.lockfile(ud.lockfile)
1867
1868            try:
1869                self.d.setVar("BB_NO_NETWORK", network)
1870                if m.verify_donestamp(ud, self.d) and not m.need_update(ud, self.d):
1871                    done = True
1872                elif m.try_premirror(ud, self.d):
1873                    logger.debug("Trying PREMIRRORS")
1874                    mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
1875                    done = m.try_mirrors(self, ud, self.d, mirrors)
1876                    if done:
1877                        try:
1878                            # early checksum verification so that if the checksum of the premirror
1879                            # contents mismatch the fetcher can still try upstream and mirrors
1880                            m.update_donestamp(ud, self.d)
1881                        except ChecksumError as e:
1882                            logger.warning("Checksum failure encountered with premirror download of %s - will attempt other sources." % u)
1883                            logger.debug(str(e))
1884                            done = False
1885
1886                if premirroronly:
1887                    self.d.setVar("BB_NO_NETWORK", "1")
1888
1889                firsterr = None
1890                verified_stamp = False
1891                if done:
1892                    verified_stamp = m.verify_donestamp(ud, self.d)
1893                if not done and (not verified_stamp or m.need_update(ud, self.d)):
1894                    try:
1895                        if not trusted_network(self.d, ud.url):
1896                            raise UntrustedUrl(ud.url)
1897                        logger.debug("Trying Upstream")
1898                        m.download(ud, self.d)
1899                        if hasattr(m, "build_mirror_data"):
1900                            m.build_mirror_data(ud, self.d)
1901                        done = True
1902                        # early checksum verify, so that if checksum mismatched,
1903                        # fetcher still have chance to fetch from mirror
1904                        m.update_donestamp(ud, self.d)
1905
1906                    except bb.fetch2.NetworkAccess:
1907                        raise
1908
1909                    except BBFetchException as e:
1910                        if isinstance(e, ChecksumError):
1911                            logger.warning("Checksum failure encountered with download of %s - will attempt other sources if available" % u)
1912                            logger.debug(str(e))
1913                            if os.path.exists(ud.localpath):
1914                                rename_bad_checksum(ud, e.checksum)
1915                        elif isinstance(e, NoChecksumError):
1916                            raise
1917                        else:
1918                            logger.warning('Failed to fetch URL %s, attempting MIRRORS if available' % u)
1919                            logger.debug(str(e))
1920                        firsterr = e
1921                        # Remove any incomplete fetch
1922                        if not verified_stamp and m.cleanup_upon_failure():
1923                            m.clean(ud, self.d)
1924                        logger.debug("Trying MIRRORS")
1925                        mirrors = mirror_from_string(self.d.getVar('MIRRORS'))
1926                        done = m.try_mirrors(self, ud, self.d, mirrors)
1927
1928                if not done or not m.done(ud, self.d):
1929                    if firsterr:
1930                        logger.error(str(firsterr))
1931                    raise FetchError("Unable to fetch URL from any source.", u)
1932
1933                m.update_donestamp(ud, self.d)
1934
1935            except IOError as e:
1936                if e.errno in [errno.ESTALE]:
1937                    logger.error("Stale Error Observed %s." % u)
1938                    raise ChecksumError("Stale Error Detected")
1939
1940            except BBFetchException as e:
1941                if isinstance(e, NoChecksumError):
1942                    (message, _) = e.args
1943                    checksum_missing_messages.append(message)
1944                    continue
1945                elif isinstance(e, ChecksumError):
1946                    logger.error("Checksum failure fetching %s" % u)
1947                raise
1948
1949            finally:
1950                if ud.lockfile:
1951                    bb.utils.unlockfile(lf)
1952        if checksum_missing_messages:
1953            logger.error("Missing SRC_URI checksum, please add those to the recipe: \n%s", "\n".join(checksum_missing_messages))
1954            raise BBFetchException("There was some missing checksums in the recipe")
1955
1956    def checkstatus(self, urls=None):
1957        """
1958        Check all URLs exist upstream.
1959
1960        Returns None if the URLs exist, raises FetchError if the check wasn't
1961        successful but there wasn't an error (such as file not found), and
1962        raises other exceptions in error cases.
1963        """
1964
1965        if not urls:
1966            urls = self.urls
1967
1968        for u in urls:
1969            ud = self.ud[u]
1970            ud.setup_localpath(self.d)
1971            m = ud.method
1972            logger.debug("Testing URL %s", u)
1973            # First try checking uri, u, from PREMIRRORS
1974            mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
1975            ret = m.try_mirrors(self, ud, self.d, mirrors, True)
1976            if not ret:
1977                # Next try checking from the original uri, u
1978                ret = m.checkstatus(self, ud, self.d)
1979                if not ret:
1980                    # Finally, try checking uri, u, from MIRRORS
1981                    mirrors = mirror_from_string(self.d.getVar('MIRRORS'))
1982                    ret = m.try_mirrors(self, ud, self.d, mirrors, True)
1983
1984            if not ret:
1985                raise FetchError("URL doesn't work", u)
1986
1987    def unpack(self, root, urls=None):
1988        """
1989        Unpack urls to root
1990        """
1991
1992        if not urls:
1993            urls = self.urls
1994
1995        unpack_tracer.start(root, self.ud, self.d)
1996
1997        for u in urls:
1998            ud = self.ud[u]
1999            ud.setup_localpath(self.d)
2000
2001            if ud.lockfile:
2002                lf = bb.utils.lockfile(ud.lockfile)
2003
2004            unpack_tracer.start_url(u)
2005            ud.method.unpack(ud, root, self.d)
2006            unpack_tracer.finish_url(u)
2007
2008            if ud.lockfile:
2009                bb.utils.unlockfile(lf)
2010
2011        unpack_tracer.complete()
2012
2013    def clean(self, urls=None):
2014        """
2015        Clean files that the fetcher gets or places
2016        """
2017
2018        if not urls:
2019            urls = self.urls
2020
2021        for url in urls:
2022            if url not in self.ud:
2023                self.ud[url] = FetchData(url, self.d)
2024            ud = self.ud[url]
2025            ud.setup_localpath(self.d)
2026
2027            if not ud.localfile and ud.localpath is None:
2028                continue
2029
2030            if ud.lockfile:
2031                lf = bb.utils.lockfile(ud.lockfile)
2032
2033            ud.method.clean(ud, self.d)
2034            if ud.donestamp:
2035                bb.utils.remove(ud.donestamp)
2036
2037            if ud.lockfile:
2038                bb.utils.unlockfile(lf)
2039
2040    def expanded_urldata(self, urls=None):
2041        """
2042        Get an expanded list of FetchData objects covering both the given
2043        URLS and any additional implicit URLs that are added automatically by
2044        the appropriate FetchMethod.
2045        """
2046
2047        if not urls:
2048            urls = self.urls
2049
2050        urldata = []
2051        for url in urls:
2052            ud = self.ud[url]
2053            urldata.append(ud)
2054            urldata += ud.method.implicit_urldata(ud, self.d)
2055
2056        return urldata
2057
2058class FetchConnectionCache(object):
2059    """
2060        A class which represents an container for socket connections.
2061    """
2062    def __init__(self):
2063        self.cache = {}
2064
2065    def get_connection_name(self, host, port):
2066        return host + ':' + str(port)
2067
2068    def add_connection(self, host, port, connection):
2069        cn = self.get_connection_name(host, port)
2070
2071        if cn not in self.cache:
2072            self.cache[cn] = connection
2073
2074    def get_connection(self, host, port):
2075        connection = None
2076
2077        cn = self.get_connection_name(host, port)
2078        if cn in self.cache:
2079            connection = self.cache[cn]
2080
2081        return connection
2082
2083    def remove_connection(self, host, port):
2084        cn = self.get_connection_name(host, port)
2085        if cn in self.cache:
2086            self.cache[cn].close()
2087            del self.cache[cn]
2088
2089    def close_connections(self):
2090        for cn in list(self.cache.keys()):
2091            self.cache[cn].close()
2092            del self.cache[cn]
2093
2094from . import cvs
2095from . import git
2096from . import gitsm
2097from . import gitannex
2098from . import local
2099from . import svn
2100from . import wget
2101from . import ssh
2102from . import sftp
2103from . import s3
2104from . import perforce
2105from . import bzr
2106from . import hg
2107from . import osc
2108from . import repo
2109from . import clearcase
2110from . import npm
2111from . import npmsw
2112from . import az
2113from . import crate
2114from . import gcp
2115from . import gomod
2116
2117methods.append(local.Local())
2118methods.append(wget.Wget())
2119methods.append(svn.Svn())
2120methods.append(git.Git())
2121methods.append(gitsm.GitSM())
2122methods.append(gitannex.GitANNEX())
2123methods.append(cvs.Cvs())
2124methods.append(ssh.SSH())
2125methods.append(sftp.SFTP())
2126methods.append(s3.S3())
2127methods.append(perforce.Perforce())
2128methods.append(bzr.Bzr())
2129methods.append(hg.Hg())
2130methods.append(osc.Osc())
2131methods.append(repo.Repo())
2132methods.append(clearcase.ClearCase())
2133methods.append(npm.Npm())
2134methods.append(npmsw.NpmShrinkWrap())
2135methods.append(az.Az())
2136methods.append(crate.Crate())
2137methods.append(gcp.GCP())
2138methods.append(gomod.GoMod())
2139methods.append(gomod.GoModGit())
2140