xref: /openbmc/openbmc/poky/bitbake/lib/bb/fetch2/__init__.py (revision c9537f57ab488bf5d90132917b0184e2527970a5)
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:
810        autoinc, rev = getattr(urldata[scms[0]].method, method_name)(urldata[scms[0]], d, urldata[scms[0]].name)
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        autoinc, rev = getattr(ud.method, method_name)(ud, d, ud.name)
832        revs.append(rev)
833        seenautoinc = seenautoinc or autoinc
834        if len(rev) > 10:
835            rev = rev[:10]
836        name_to_rev[ud.name] = rev
837    # Replace names by revisions in the SRCREV_FORMAT string. The approach used
838    # here can handle names being prefixes of other names and names appearing
839    # as substrings in revisions (in which case the name should not be
840    # expanded). The '|' regular expression operator tries matches from left to
841    # right, so we need to sort the names with the longest ones first.
842    names_descending_len = sorted(name_to_rev, key=len, reverse=True)
843    name_to_rev_re = "|".join(re.escape(name) for name in names_descending_len)
844    format = re.sub(name_to_rev_re, lambda match: name_to_rev[match.group(0)], format)
845
846    if seenautoinc:
847        format = "AUTOINC+" + format
848
849    d.delVar("__BBINSRCREV")
850    return format, revs
851
852def get_hashvalue(d, method_name='sortable_revision'):
853    pkgv, revs = _get_srcrev(d, method_name=method_name)
854    return " ".join(revs)
855
856def get_pkgv_string(d, method_name='sortable_revision'):
857    pkgv, revs = _get_srcrev(d, method_name=method_name)
858    return pkgv
859
860def get_srcrev(d, method_name='sortable_revision'):
861    pkgv, revs = _get_srcrev(d, method_name=method_name)
862    if not pkgv:
863        raise FetchError("SRCREV was used yet no valid SCM was found in SRC_URI")
864    return pkgv
865
866def localpath(url, d):
867    fetcher = bb.fetch2.Fetch([url], d)
868    return fetcher.localpath(url)
869
870# Need to export PATH as binary could be in metadata paths
871# rather than host provided
872# Also include some other variables.
873FETCH_EXPORT_VARS = ['HOME', 'PATH',
874                     'HTTP_PROXY', 'http_proxy',
875                     'HTTPS_PROXY', 'https_proxy',
876                     'FTP_PROXY', 'ftp_proxy',
877                     'FTPS_PROXY', 'ftps_proxy',
878                     'NO_PROXY', 'no_proxy',
879                     'ALL_PROXY', 'all_proxy',
880                     'GIT_PROXY_COMMAND',
881                     'GIT_SSH',
882                     'GIT_SSH_COMMAND',
883                     'GIT_SSL_CAINFO',
884                     'GIT_SMART_HTTP',
885                     'SSH_AUTH_SOCK', 'SSH_AGENT_PID',
886                     'SOCKS5_USER', 'SOCKS5_PASSWD',
887                     'DBUS_SESSION_BUS_ADDRESS',
888                     'P4CONFIG',
889                     'SSL_CERT_FILE',
890                     'NODE_EXTRA_CA_CERTS',
891                     'AWS_PROFILE',
892                     'AWS_ACCESS_KEY_ID',
893                     'AWS_SECRET_ACCESS_KEY',
894                     'AWS_ROLE_ARN',
895                     'AWS_WEB_IDENTITY_TOKEN_FILE',
896                     'AWS_DEFAULT_REGION',
897                     'AWS_SESSION_TOKEN',
898                     'GIT_CACHE_PATH',
899                     'REMOTE_CONTAINERS_IPC',
900                     'GITHUB_TOKEN',
901                     'SSL_CERT_DIR']
902
903def get_fetcher_environment(d):
904    newenv = {}
905    origenv = d.getVar("BB_ORIGENV")
906    for name in bb.fetch2.FETCH_EXPORT_VARS:
907        value = d.getVar(name)
908        if not value and origenv:
909            value = origenv.getVar(name)
910        if value:
911            newenv[name] = value
912    return newenv
913
914def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None):
915    """
916    Run cmd returning the command output
917    Raise an error if interrupted or cmd fails
918    Optionally echo command output to stdout
919    Optionally remove the files/directories listed in cleanup upon failure
920    """
921
922    exportvars = FETCH_EXPORT_VARS
923
924    if not cleanup:
925        cleanup = []
926
927    # If PATH contains WORKDIR which contains PV-PR which contains SRCPV we
928    # can end up in circular recursion here so give the option of breaking it
929    # in a data store copy.
930    try:
931        d.getVar("PV")
932        d.getVar("PR")
933    except bb.data_smart.ExpansionError:
934        d = bb.data.createCopy(d)
935        d.setVar("PV", "fetcheravoidrecurse")
936        d.setVar("PR", "fetcheravoidrecurse")
937
938    origenv = d.getVar("BB_ORIGENV", False)
939    for var in exportvars:
940        val = d.getVar(var) or (origenv and origenv.getVar(var))
941        if val:
942            cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd)
943
944    # Disable pseudo as it may affect ssh, potentially causing it to hang.
945    cmd = 'export PSEUDO_DISABLED=1; ' + cmd
946
947    if workdir:
948        logger.debug("Running '%s' in %s" % (cmd, workdir))
949    else:
950        logger.debug("Running %s", cmd)
951
952    success = False
953    error_message = ""
954
955    try:
956        (output, errors) = bb.process.run(cmd, log=log, shell=True, stderr=subprocess.PIPE, cwd=workdir)
957        success = True
958    except bb.process.NotFoundError as e:
959        error_message = "Fetch command %s not found" % (e.command)
960    except bb.process.ExecutionError as e:
961        if e.stdout:
962            output = "output:\n%s\n%s" % (e.stdout, e.stderr)
963        elif e.stderr:
964            output = "output:\n%s" % e.stderr
965        else:
966            if log:
967                output = "see logfile for output"
968            else:
969                output = "no output"
970        error_message = "Fetch command %s failed with exit code %s, %s" % (e.command, e.exitcode, output)
971    except bb.process.CmdError as e:
972        error_message = "Fetch command %s could not be run:\n%s" % (e.command, e.msg)
973    if not success:
974        for f in cleanup:
975            try:
976                bb.utils.remove(f, True)
977            except OSError:
978                pass
979
980        raise FetchError(error_message)
981
982    return output
983
984def check_network_access(d, info, url):
985    """
986    log remote network access, and error if BB_NO_NETWORK is set or the given
987    URI is untrusted
988    """
989    if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")):
990        raise NetworkAccess(url, info)
991    elif not trusted_network(d, url):
992        raise UntrustedUrl(url, info)
993    else:
994        logger.debug("Fetcher accessed the network with the command %s" % info)
995
996def build_mirroruris(origud, mirrors, ld):
997    uris = []
998    uds = []
999
1000    replacements = {}
1001    replacements["TYPE"] = origud.type
1002    replacements["HOST"] = origud.host
1003    replacements["PATH"] = origud.path
1004    replacements["BASENAME"] = origud.path.split("/")[-1]
1005    replacements["MIRRORNAME"] = origud.host.replace(':','.') + origud.path.replace('/', '.').replace('*', '.')
1006
1007    def adduri(ud, uris, uds, mirrors, tarballs):
1008        for line in mirrors:
1009            try:
1010                (find, replace) = line
1011            except ValueError:
1012                continue
1013
1014            for tarball in tarballs:
1015                newuri = uri_replace(ud, find, replace, replacements, ld, tarball)
1016                if not newuri or newuri in uris or newuri == origud.url:
1017                    continue
1018
1019                if not trusted_network(ld, newuri):
1020                    logger.debug("Mirror %s not in the list of trusted networks, skipping" %  (newuri))
1021                    continue
1022
1023                # Create a local copy of the mirrors minus the current line
1024                # this will prevent us from recursively processing the same line
1025                # as well as indirect recursion A -> B -> C -> A
1026                localmirrors = list(mirrors)
1027                localmirrors.remove(line)
1028
1029                try:
1030                    newud = FetchData(newuri, ld)
1031                    newud.ignore_checksums = True
1032                    newud.setup_localpath(ld)
1033                except bb.fetch2.BBFetchException as e:
1034                    logger.debug("Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
1035                    logger.debug(str(e))
1036                    try:
1037                        # setup_localpath of file:// urls may fail, we should still see
1038                        # if mirrors of the url exist
1039                        adduri(newud, uris, uds, localmirrors, tarballs)
1040                    except UnboundLocalError:
1041                        pass
1042                    continue
1043                uris.append(newuri)
1044                uds.append(newud)
1045
1046                adduri(newud, uris, uds, localmirrors, tarballs)
1047
1048    adduri(origud, uris, uds, mirrors, origud.mirrortarballs or [None])
1049
1050    return uris, uds
1051
1052def rename_bad_checksum(ud, suffix):
1053    """
1054    Renames files to have suffix from parameter
1055    """
1056
1057    if ud.localpath is None:
1058        return
1059
1060    new_localpath = "%s_bad-checksum_%s" % (ud.localpath, suffix)
1061    bb.warn("Renaming %s to %s" % (ud.localpath, new_localpath))
1062    if not bb.utils.movefile(ud.localpath, new_localpath):
1063        bb.warn("Renaming %s to %s failed, grep movefile in log.do_fetch to see why" % (ud.localpath, new_localpath))
1064
1065
1066def try_mirror_url(fetch, origud, ud, ld, check = False):
1067    # Return of None or a value means we're finished
1068    # False means try another url
1069
1070    if ud.lockfile and ud.lockfile != origud.lockfile:
1071        lf = bb.utils.lockfile(ud.lockfile)
1072
1073    try:
1074        if check:
1075            found = ud.method.checkstatus(fetch, ud, ld)
1076            if found:
1077                return found
1078            return False
1079
1080        if not verify_donestamp(ud, ld, origud) or ud.method.need_update(ud, ld):
1081            ud.method.download(ud, ld)
1082            if hasattr(ud.method,"build_mirror_data"):
1083                ud.method.build_mirror_data(ud, ld)
1084
1085        if not ud.localpath or not os.path.exists(ud.localpath):
1086            return False
1087
1088        if ud.localpath == origud.localpath:
1089            return ud.localpath
1090
1091        # We may be obtaining a mirror tarball which needs further processing by the real fetcher
1092        # If that tarball is a local file:// we need to provide a symlink to it
1093        dldir = ld.getVar("DL_DIR")
1094
1095        if bb.utils.to_boolean(ld.getVar("BB_FETCH_PREMIRRORONLY")):
1096            ld = ld.createCopy()
1097            ld.setVar("BB_NO_NETWORK", "1")
1098
1099        if origud.mirrortarballs and os.path.basename(ud.localpath) in origud.mirrortarballs and os.path.basename(ud.localpath) != os.path.basename(origud.localpath):
1100            # Create donestamp in old format to avoid triggering a re-download
1101            if ud.donestamp:
1102                bb.utils.mkdirhier(os.path.dirname(ud.donestamp))
1103                open(ud.donestamp, 'w').close()
1104            dest = os.path.join(dldir, os.path.basename(ud.localpath))
1105            if not os.path.exists(dest):
1106                # In case this is executing without any file locks held (as is
1107                # the case for file:// URLs), two tasks may end up here at the
1108                # same time, in which case we do not want the second task to
1109                # fail when the link has already been created by the first task.
1110                try:
1111                    os.symlink(ud.localpath, dest)
1112                except FileExistsError:
1113                    pass
1114            if not verify_donestamp(origud, ld) or origud.method.need_update(origud, ld):
1115                origud.method.download(origud, ld)
1116                if hasattr(origud.method, "build_mirror_data"):
1117                    origud.method.build_mirror_data(origud, ld)
1118            return origud.localpath
1119        # Otherwise the result is a local file:// and we symlink to it
1120        # This may also be a link to a shallow archive
1121        # When using shallow mode, add a symlink to the original fullshallow
1122        # path to ensure a valid symlink even in the `PREMIRRORS` case
1123        origud.method.update_mirror_links(ud, origud)
1124        update_stamp(origud, ld)
1125        return ud.localpath
1126
1127    except bb.fetch2.NetworkAccess:
1128        raise
1129
1130    except IOError as e:
1131        if e.errno in [errno.ESTALE]:
1132            logger.warning("Stale Error Observed %s." % ud.url)
1133            return False
1134        raise
1135
1136    except bb.fetch2.BBFetchException as e:
1137        if isinstance(e, ChecksumError):
1138            logger.warning("Mirror checksum failure for url %s (original url: %s)\nCleaning and trying again." % (ud.url, origud.url))
1139            logger.warning(str(e))
1140            if os.path.exists(ud.localpath):
1141                rename_bad_checksum(ud, e.checksum)
1142        elif isinstance(e, NoChecksumError):
1143            raise
1144        else:
1145            logger.debug("Mirror fetch failure for url %s (original url: %s)" % (ud.url, origud.url))
1146            logger.debug(str(e))
1147        try:
1148            if ud.method.cleanup_upon_failure():
1149                ud.method.clean(ud, ld)
1150        except UnboundLocalError:
1151            pass
1152        return False
1153    finally:
1154        if ud.lockfile and ud.lockfile != origud.lockfile:
1155            bb.utils.unlockfile(lf)
1156
1157def try_mirrors(fetch, d, origud, mirrors, check = False):
1158    """
1159    Try to use a mirrored version of the sources.
1160    This method will be automatically called before the fetchers go.
1161
1162    d Is a bb.data instance
1163    uri is the original uri we're trying to download
1164    mirrors is the list of mirrors we're going to try
1165    """
1166    ld = d.createCopy()
1167
1168    uris, uds = build_mirroruris(origud, mirrors, ld)
1169
1170    for index, uri in enumerate(uris):
1171        ret = try_mirror_url(fetch, origud, uds[index], ld, check)
1172        if ret:
1173            return ret
1174    return None
1175
1176def trusted_network(d, url):
1177    """
1178    Use a trusted url during download if networking is enabled and
1179    BB_ALLOWED_NETWORKS is set globally or for a specific recipe.
1180    Note: modifies SRC_URI & mirrors.
1181    """
1182    if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")):
1183        return True
1184
1185    pkgname = d.getVar('PN')
1186    trusted_hosts = None
1187    if pkgname:
1188        trusted_hosts = d.getVarFlag('BB_ALLOWED_NETWORKS', pkgname, False)
1189
1190    if not trusted_hosts:
1191        trusted_hosts = d.getVar('BB_ALLOWED_NETWORKS')
1192
1193    # Not enabled.
1194    if not trusted_hosts:
1195        return True
1196
1197    scheme, network, path, user, passwd, param = decodeurl(url)
1198
1199    if not network:
1200        return True
1201
1202    network = network.split(':')[0]
1203    network = network.lower()
1204
1205    for host in trusted_hosts.split(" "):
1206        host = host.lower()
1207        if host.startswith("*.") and ("." + network).endswith(host[1:]):
1208            return True
1209        if host == network:
1210            return True
1211
1212    return False
1213
1214def srcrev_internal_helper(ud, d, name):
1215    """
1216    Return:
1217        a) a source revision if specified
1218        b) latest revision if SRCREV="AUTOINC"
1219        c) None if not specified
1220    """
1221
1222    srcrev = None
1223    pn = d.getVar("PN")
1224    attempts = []
1225    if name != '' and pn:
1226        attempts.append("SRCREV_%s:pn-%s" % (name, pn))
1227    if name != '':
1228        attempts.append("SRCREV_%s" % name)
1229    if pn:
1230        attempts.append("SRCREV:pn-%s" % pn)
1231    attempts.append("SRCREV")
1232
1233    for a in attempts:
1234        srcrev = d.getVar(a)
1235        if srcrev and srcrev != "INVALID":
1236            break
1237
1238    if 'rev' in ud.parm:
1239        parmrev = ud.parm['rev']
1240        if srcrev == "INVALID" or not srcrev:
1241            return parmrev
1242        if srcrev != parmrev:
1243            raise FetchError("Conflicting revisions (%s from SRCREV and %s from the url) found, please specify one valid value" % (srcrev, parmrev))
1244        return parmrev
1245
1246    if 'tag' in ud.parm and (srcrev == "INVALID" or not srcrev):
1247        return ud.parm['tag']
1248
1249    if srcrev == "INVALID" or not srcrev:
1250        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)
1251    if srcrev == "AUTOINC":
1252        d.setVar("__BBAUTOREV_ACTED_UPON", True)
1253        srcrev = ud.method.latest_revision(ud, d, name)
1254
1255    return srcrev
1256
1257def get_checksum_file_list(d):
1258    """ Get a list of files checksum in SRC_URI
1259
1260    Returns the resolved local paths of all local file entries in
1261    SRC_URI as a space-separated string
1262    """
1263    fetch = Fetch([], d, cache = False, localonly = True)
1264    filelist = []
1265    for u in fetch.urls:
1266        ud = fetch.ud[u]
1267        if ud and isinstance(ud.method, local.Local):
1268            found = False
1269            paths = ud.method.localfile_searchpaths(ud, d)
1270            for f in paths:
1271                pth = ud.path
1272                if os.path.exists(f):
1273                    found = True
1274                filelist.append(f + ":" + str(os.path.exists(f)))
1275            if not found:
1276                bb.fatal(("Unable to get checksum for %s SRC_URI entry %s: file could not be found"
1277                            "\nThe following paths were searched:"
1278                            "\n%s") % (d.getVar('PN'), os.path.basename(f), '\n'.join(paths)))
1279
1280    return " ".join(filelist)
1281
1282def get_file_checksums(filelist, pn, localdirsexclude):
1283    """Get a list of the checksums for a list of local files
1284
1285    Returns the checksums for a list of local files, caching the results as
1286    it proceeds
1287
1288    """
1289    return _checksum_cache.get_checksums(filelist, pn, localdirsexclude)
1290
1291
1292class FetchData(object):
1293    """
1294    A class which represents the fetcher state for a given URI.
1295    """
1296    def __init__(self, url, d, localonly = False):
1297        # localpath is the location of a downloaded result. If not set, the file is local.
1298        self.donestamp = None
1299        self.needdonestamp = True
1300        self.localfile = ""
1301        self.localpath = None
1302        self.lockfile = None
1303        self.mirrortarballs = []
1304        self.basename = None
1305        self.basepath = None
1306        (self.type, self.host, self.path, self.user, self.pswd, self.parm) = decodeurl(d.expand(url))
1307        self.date = self.getSRCDate(d)
1308        self.url = url
1309        if not self.user and "user" in self.parm:
1310            self.user = self.parm["user"]
1311        if not self.pswd and "pswd" in self.parm:
1312            self.pswd = self.parm["pswd"]
1313        self.setup = False
1314
1315        def configure_checksum(checksum_id):
1316            checksum_plain_name = "%ssum" % checksum_id
1317            if "name" in self.parm:
1318                checksum_name = "%s.%ssum" % (self.parm["name"], checksum_id)
1319            else:
1320                checksum_name = checksum_plain_name
1321
1322            if checksum_name in self.parm:
1323                checksum_expected = self.parm[checksum_name]
1324            elif checksum_plain_name in self.parm:
1325                checksum_expected = self.parm[checksum_plain_name]
1326                checksum_name = checksum_plain_name
1327            elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3", "az", "crate", "gs", "gomod", "npm"]:
1328                checksum_expected = None
1329            else:
1330                checksum_expected = d.getVarFlag("SRC_URI", checksum_name)
1331
1332            setattr(self, "%s_name" % checksum_id, checksum_name)
1333            setattr(self, "%s_expected" % checksum_id, checksum_expected)
1334
1335        self.name = self.parm.get("name",'default')
1336        if "," in self.name:
1337            raise ParameterError("The fetcher no longer supports multiple name parameters in a single url", self.url)
1338
1339        self.method = None
1340        for m in methods:
1341            if m.supports(self, d):
1342                self.method = m
1343                break
1344
1345        if not self.method:
1346            raise NoMethodError(url)
1347
1348        if localonly and not isinstance(self.method, local.Local):
1349            raise NonLocalMethod()
1350
1351        if self.parm.get("proto", None) and "protocol" not in self.parm:
1352            logger.warning('Consider updating %s recipe to use "protocol" not "proto" in SRC_URI.', d.getVar('PN'))
1353            self.parm["protocol"] = self.parm.get("proto", None)
1354
1355        if hasattr(self.method, "urldata_init"):
1356            self.method.urldata_init(self, d)
1357
1358        for checksum_id in CHECKSUM_LIST:
1359            configure_checksum(checksum_id)
1360
1361        self.ignore_checksums = False
1362
1363        if "localpath" in self.parm:
1364            # if user sets localpath for file, use it instead.
1365            self.localpath = self.parm["localpath"]
1366            self.basename = os.path.basename(self.localpath)
1367        elif self.localfile:
1368            self.localpath = self.method.localpath(self, d)
1369
1370        dldir = d.getVar("DL_DIR")
1371
1372        if not self.needdonestamp:
1373            return
1374
1375        # Note: .done and .lock files should always be in DL_DIR whereas localpath may not be.
1376        if self.localpath and self.localpath.startswith(dldir):
1377            basepath = self.localpath
1378        elif self.localpath:
1379            basepath = dldir + os.sep + os.path.basename(self.localpath)
1380        elif self.basepath or self.basename:
1381            basepath = dldir + os.sep + (self.basepath or self.basename)
1382        else:
1383            bb.fatal("Can't determine lock path for url %s" % url)
1384
1385        self.donestamp = basepath + '.done'
1386        self.lockfile = basepath + '.lock'
1387
1388    def setup_revisions(self, d):
1389        self.revision = srcrev_internal_helper(self, d, self.name)
1390
1391    def setup_localpath(self, d):
1392        if not self.localpath:
1393            self.localpath = self.method.localpath(self, d)
1394
1395    def getSRCDate(self, d):
1396        """
1397        Return the SRC Date for the component
1398
1399        d the bb.data module
1400        """
1401        if "srcdate" in self.parm:
1402            return self.parm['srcdate']
1403
1404        pn = d.getVar("PN")
1405
1406        if pn:
1407            return d.getVar("SRCDATE_%s" % pn) or d.getVar("SRCDATE") or d.getVar("DATE")
1408
1409        return d.getVar("SRCDATE") or d.getVar("DATE")
1410
1411class FetchMethod(object):
1412    """Base class for 'fetch'ing data"""
1413
1414    def __init__(self, urls=None):
1415        self.urls = []
1416
1417    def supports(self, urldata, d):
1418        """
1419        Check to see if this fetch class supports a given url.
1420        """
1421        return 0
1422
1423    def localpath(self, urldata, d):
1424        """
1425        Return the local filename of a given url assuming a successful fetch.
1426        Can also setup variables in urldata for use in go (saving code duplication
1427        and duplicate code execution)
1428        """
1429        return os.path.join(d.getVar("DL_DIR"), urldata.localfile)
1430
1431    def supports_checksum(self, urldata):
1432        """
1433        Is localpath something that can be represented by a checksum?
1434        """
1435
1436        # We cannot compute checksums for None
1437        if urldata.localpath is None:
1438            return False
1439        # We cannot compute checksums for directories
1440        if os.path.isdir(urldata.localpath):
1441            return False
1442        return True
1443
1444    def recommends_checksum(self, urldata):
1445        """
1446        Is the backend on where checksumming is recommended (should warnings
1447        be displayed if there is no checksum)?
1448        """
1449        return False
1450
1451    def cleanup_upon_failure(self):
1452        """
1453        When a fetch fails, should clean() be called?
1454        """
1455        return True
1456
1457    def verify_donestamp(self, ud, d):
1458        """
1459        Verify the donestamp file
1460        """
1461        return verify_donestamp(ud, d)
1462
1463    def update_donestamp(self, ud, d):
1464        """
1465        Update the donestamp file
1466        """
1467        update_stamp(ud, d)
1468
1469    def _strip_leading_slashes(self, relpath):
1470        """
1471        Remove leading slash as os.path.join can't cope
1472        """
1473        while os.path.isabs(relpath):
1474            relpath = relpath[1:]
1475        return relpath
1476
1477    def setUrls(self, urls):
1478        self.__urls = urls
1479
1480    def getUrls(self):
1481        return self.__urls
1482
1483    urls = property(getUrls, setUrls, None, "Urls property")
1484
1485    def need_update(self, ud, d):
1486        """
1487        Force a fetch, even if localpath exists?
1488        """
1489        if os.path.exists(ud.localpath):
1490            return False
1491        return True
1492
1493    def supports_srcrev(self):
1494        """
1495        The fetcher supports auto source revisions (SRCREV)
1496        """
1497        return False
1498
1499    def download(self, urldata, d):
1500        """
1501        Fetch urls
1502        Assumes localpath was called first
1503        """
1504        raise NoMethodError(urldata.url)
1505
1506    def unpack(self, urldata, rootdir, data):
1507        iterate = False
1508        file = urldata.localpath
1509
1510        try:
1511            unpack = bb.utils.to_boolean(urldata.parm.get('unpack'), True)
1512        except ValueError as exc:
1513            bb.fatal("Invalid value for 'unpack' parameter for %s: %s" %
1514                     (file, urldata.parm.get('unpack')))
1515
1516        base, ext = os.path.splitext(file)
1517        if ext in ['.gz', '.bz2', '.Z', '.xz', '.lz', '.zst']:
1518            efile = os.path.join(rootdir, os.path.basename(base))
1519        else:
1520            efile = file
1521        cmd = None
1522
1523        if unpack:
1524            tar_cmd = 'tar --extract --no-same-owner'
1525            if 'striplevel' in urldata.parm:
1526                tar_cmd += ' --strip-components=%s' %  urldata.parm['striplevel']
1527            if file.endswith('.tar'):
1528                cmd = '%s -f %s' % (tar_cmd, file)
1529            elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
1530                cmd = '%s -z -f %s' % (tar_cmd, file)
1531            elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
1532                cmd = 'bzip2 -dc %s | %s -f -' % (file, tar_cmd)
1533            elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
1534                cmd = 'gzip -dc %s > %s' % (file, efile)
1535            elif file.endswith('.bz2'):
1536                cmd = 'bzip2 -dc %s > %s' % (file, efile)
1537            elif file.endswith('.txz') or file.endswith('.tar.xz'):
1538                cmd = 'xz -dc %s | %s -f -' % (file, tar_cmd)
1539            elif file.endswith('.xz'):
1540                cmd = 'xz -dc %s > %s' % (file, efile)
1541            elif file.endswith('.tar.lz'):
1542                cmd = 'lzip -dc %s | %s -f -' % (file, tar_cmd)
1543            elif file.endswith('.lz'):
1544                cmd = 'lzip -dc %s > %s' % (file, efile)
1545            elif file.endswith('.tar.7z'):
1546                cmd = '7z x -so %s | %s -f -' % (file, tar_cmd)
1547            elif file.endswith('.7z'):
1548                cmd = '7za x -y %s 1>/dev/null' % file
1549            elif file.endswith('.tzst') or file.endswith('.tar.zst'):
1550                cmd = 'zstd --decompress --stdout %s | %s -f -' % (file, tar_cmd)
1551            elif file.endswith('.zst'):
1552                cmd = 'zstd --decompress --stdout %s > %s' % (file, efile)
1553            elif file.endswith('.zip') or file.endswith('.jar'):
1554                try:
1555                    dos = bb.utils.to_boolean(urldata.parm.get('dos'), False)
1556                except ValueError as exc:
1557                    bb.fatal("Invalid value for 'dos' parameter for %s: %s" %
1558                             (file, urldata.parm.get('dos')))
1559                cmd = 'unzip -q -o'
1560                if dos:
1561                    cmd = '%s -a' % cmd
1562                cmd = "%s '%s'" % (cmd, file)
1563            elif file.endswith('.rpm') or file.endswith('.srpm'):
1564                if 'extract' in urldata.parm:
1565                    unpack_file = urldata.parm.get('extract')
1566                    cmd = 'rpm2cpio.sh %s | cpio -id %s' % (file, unpack_file)
1567                    iterate = True
1568                    iterate_file = unpack_file
1569                else:
1570                    cmd = 'rpm2cpio.sh %s | cpio -id' % (file)
1571            elif file.endswith('.deb') or file.endswith('.ipk'):
1572                output = subprocess.check_output(['ar', '-t', file], preexec_fn=subprocess_setup)
1573                datafile = None
1574                if output:
1575                    for line in output.decode().splitlines():
1576                        if line.startswith('data.tar.') or line == 'data.tar':
1577                            datafile = line
1578                            break
1579                    else:
1580                        raise UnpackError("Unable to unpack deb/ipk package - does not contain data.tar* file", urldata.url)
1581                else:
1582                    raise UnpackError("Unable to unpack deb/ipk package - could not list contents", urldata.url)
1583                cmd = 'ar x %s %s && %s -p -f %s && rm %s' % (file, datafile, tar_cmd, datafile, datafile)
1584
1585        # If 'subdir' param exists, create a dir and use it as destination for unpack cmd
1586        if 'subdir' in urldata.parm:
1587            subdir = urldata.parm.get('subdir')
1588            if os.path.isabs(subdir):
1589                if not os.path.realpath(subdir).startswith(os.path.realpath(rootdir)):
1590                    raise UnpackError("subdir argument isn't a subdirectory of unpack root %s" % rootdir, urldata.url)
1591                unpackdir = subdir
1592            else:
1593                unpackdir = os.path.join(rootdir, subdir)
1594            bb.utils.mkdirhier(unpackdir)
1595        else:
1596            unpackdir = rootdir
1597
1598        if not unpack or not cmd:
1599            urldata.unpack_tracer.unpack("file-copy", unpackdir)
1600            # If file == dest, then avoid any copies, as we already put the file into dest!
1601            dest = os.path.join(unpackdir, os.path.basename(file))
1602            if file != dest and not (os.path.exists(dest) and os.path.samefile(file, dest)):
1603                destdir = '.'
1604                # For file:// entries all intermediate dirs in path must be created at destination
1605                if urldata.type == "file":
1606                    # Trailing '/' does a copying to wrong place
1607                    urlpath = urldata.path.rstrip('/')
1608                    # Want files places relative to cwd so no leading '/'
1609                    urlpath = urlpath.lstrip('/')
1610                    if urlpath.find("/") != -1:
1611                        destdir = urlpath.rsplit("/", 1)[0] + '/'
1612                        bb.utils.mkdirhier("%s/%s" % (unpackdir, destdir))
1613                cmd = 'cp --force --preserve=timestamps --no-dereference --recursive -H "%s" "%s"' % (file, destdir)
1614        else:
1615            urldata.unpack_tracer.unpack("archive-extract", unpackdir)
1616
1617        if not cmd:
1618            return
1619
1620        path = data.getVar('PATH')
1621        if path:
1622            cmd = "PATH=\"%s\" %s" % (path, cmd)
1623        bb.note("Unpacking %s to %s/" % (file, unpackdir))
1624        ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True, cwd=unpackdir)
1625
1626        if ret != 0:
1627            raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), urldata.url)
1628
1629        if iterate is True:
1630            iterate_urldata = urldata
1631            iterate_urldata.localpath = "%s/%s" % (rootdir, iterate_file)
1632            self.unpack(urldata, rootdir, data)
1633
1634        return
1635
1636    def clean(self, urldata, d):
1637        """
1638        Clean any existing full or partial download
1639        """
1640        bb.utils.remove(urldata.localpath)
1641
1642    def ensure_symlink(self, target, link_name):
1643        if not os.path.exists(link_name):
1644            dirname = os.path.dirname(link_name)
1645            bb.utils.mkdirhier(dirname)
1646            if os.path.islink(link_name):
1647                # Broken symbolic link
1648                os.unlink(link_name)
1649
1650            # In case this is executing without any file locks held (as is
1651            # the case for file:// URLs), two tasks may end up here at the
1652            # same time, in which case we do not want the second task to
1653            # fail when the link has already been created by the first task.
1654            try:
1655                os.symlink(target, link_name)
1656            except FileExistsError:
1657                pass
1658
1659    def update_mirror_links(self, ud, origud):
1660        # For local file:// results, create a symlink to them
1661        # This may also be a link to a shallow archive
1662        self.ensure_symlink(ud.localpath, origud.localpath)
1663
1664    def try_premirror(self, urldata, d):
1665        """
1666        Should premirrors be used?
1667        """
1668        return True
1669
1670    def try_mirrors(self, fetch, urldata, d, mirrors, check=False):
1671        """
1672        Try to use a mirror
1673        """
1674        return bool(try_mirrors(fetch, d, urldata, mirrors, check))
1675
1676    def checkstatus(self, fetch, urldata, d):
1677        """
1678        Check the status of a URL
1679        Assumes localpath was called first
1680        """
1681        logger.info("URL %s could not be checked for status since no method exists.", urldata.url)
1682        return True
1683
1684    def latest_revision(self, ud, d, name):
1685        """
1686        Look in the cache for the latest revision, if not present ask the SCM.
1687        """
1688        if not hasattr(self, "_latest_revision"):
1689            raise ParameterError("The fetcher for this URL does not support _latest_revision", ud.url)
1690
1691        key = self.generate_revision_key(ud, d, name)
1692
1693        rev = _revisions_cache.get_rev(key)
1694        if rev is None:
1695            rev = self._latest_revision(ud, d, name)
1696            _revisions_cache.set_rev(key, rev)
1697        return rev
1698
1699    def sortable_revision(self, ud, d, name):
1700        latest_rev = self._build_revision(ud, d, name)
1701        return True, str(latest_rev)
1702
1703    def generate_revision_key(self, ud, d, name):
1704        return self._revision_key(ud, d, name)
1705
1706    def latest_versionstring(self, ud, d):
1707        """
1708        Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
1709        by searching through the tags output of ls-remote, comparing
1710        versions and returning the highest match as a (version, revision) pair.
1711        """
1712        return ('', '')
1713
1714    def done(self, ud, d):
1715        """
1716        Is the download done ?
1717        """
1718        if os.path.exists(ud.localpath):
1719            return True
1720        return False
1721
1722    def implicit_urldata(self, ud, d):
1723        """
1724        Get a list of FetchData objects for any implicit URLs that will also
1725        be downloaded when we fetch the given URL.
1726        """
1727        return []
1728
1729
1730class DummyUnpackTracer(object):
1731    """
1732    Abstract API definition for a class that traces unpacked source files back
1733    to their respective upstream SRC_URI entries, for software composition
1734    analysis, license compliance and detailed SBOM generation purposes.
1735    User may load their own unpack tracer class (instead of the dummy
1736    one) by setting the BB_UNPACK_TRACER_CLASS config parameter.
1737    """
1738    def start(self, unpackdir, urldata_dict, d):
1739        """
1740        Start tracing the core Fetch.unpack process, using an index to map
1741        unpacked files to each SRC_URI entry.
1742        This method is called by Fetch.unpack and it may receive nested calls by
1743        gitsm and npmsw fetchers, that expand SRC_URI entries by adding implicit
1744        URLs and by recursively calling Fetch.unpack from new (nested) Fetch
1745        instances.
1746        """
1747        return
1748    def start_url(self, url):
1749        """Start tracing url unpack process.
1750        This method is called by Fetch.unpack before the fetcher-specific unpack
1751        method starts, and it may receive nested calls by gitsm and npmsw
1752        fetchers.
1753        """
1754        return
1755    def unpack(self, unpack_type, destdir):
1756        """
1757        Set unpack_type and destdir for current url.
1758        This method is called by the fetcher-specific unpack method after url
1759        tracing started.
1760        """
1761        return
1762    def finish_url(self, url):
1763        """Finish tracing url unpack process and update the file index.
1764        This method is called by Fetch.unpack after the fetcher-specific unpack
1765        method finished its job, and it may receive nested calls by gitsm
1766        and npmsw fetchers.
1767        """
1768        return
1769    def complete(self):
1770        """
1771        Finish tracing the Fetch.unpack process, and check if all nested
1772        Fecth.unpack calls (if any) have been completed; if so, save collected
1773        metadata.
1774        """
1775        return
1776
1777
1778class Fetch(object):
1779    def __init__(self, urls, d, cache = True, localonly = False, connection_cache = None):
1780        if localonly and cache:
1781            raise Exception("bb.fetch2.Fetch.__init__: cannot set cache and localonly at same time")
1782
1783        if not urls:
1784            urls = d.getVar("SRC_URI").split()
1785        self.urls = urls
1786        self.d = d
1787        self.ud = {}
1788        self.connection_cache = connection_cache
1789
1790        fn = d.getVar('FILE')
1791        mc = d.getVar('__BBMULTICONFIG') or ""
1792        key = None
1793        if cache and fn:
1794            key = mc + fn + str(id(d))
1795        if key in urldata_cache:
1796            self.ud = urldata_cache[key]
1797
1798        # the unpack_tracer object needs to be made available to possible nested
1799        # Fetch instances (when those are created by gitsm and npmsw fetchers)
1800        # so we set it as a global variable
1801        global unpack_tracer
1802        try:
1803            unpack_tracer
1804        except NameError:
1805            class_path = d.getVar("BB_UNPACK_TRACER_CLASS")
1806            if class_path:
1807                # use user-defined unpack tracer class
1808                import importlib
1809                module_name, _, class_name = class_path.rpartition(".")
1810                module = importlib.import_module(module_name)
1811                class_ = getattr(module, class_name)
1812                unpack_tracer = class_()
1813            else:
1814                # fall back to the dummy/abstract class
1815                unpack_tracer = DummyUnpackTracer()
1816
1817        for url in urls:
1818            if url not in self.ud:
1819                try:
1820                    self.ud[url] = FetchData(url, d, localonly)
1821                    self.ud[url].unpack_tracer = unpack_tracer
1822                except NonLocalMethod:
1823                    if localonly:
1824                        self.ud[url] = None
1825                        pass
1826
1827        if key:
1828            urldata_cache[key] = self.ud
1829
1830    def localpath(self, url):
1831        if url not in self.urls:
1832            self.ud[url] = FetchData(url, self.d)
1833
1834        self.ud[url].setup_localpath(self.d)
1835        return self.ud[url].localpath
1836
1837    def localpaths(self):
1838        """
1839        Return a list of the local filenames, assuming successful fetch
1840        """
1841        local = []
1842
1843        for u in self.urls:
1844            ud = self.ud[u]
1845            ud.setup_localpath(self.d)
1846            local.append(ud.localpath)
1847
1848        return local
1849
1850    def download(self, urls=None):
1851        """
1852        Fetch all urls
1853        """
1854        if not urls:
1855            urls = self.urls
1856
1857        network = self.d.getVar("BB_NO_NETWORK")
1858        premirroronly = bb.utils.to_boolean(self.d.getVar("BB_FETCH_PREMIRRORONLY"))
1859
1860        checksum_missing_messages = []
1861        for u in urls:
1862            ud = self.ud[u]
1863            ud.setup_localpath(self.d)
1864            m = ud.method
1865            done = False
1866
1867            if ud.lockfile:
1868                lf = bb.utils.lockfile(ud.lockfile)
1869
1870            try:
1871                self.d.setVar("BB_NO_NETWORK", network)
1872                if m.verify_donestamp(ud, self.d) and not m.need_update(ud, self.d):
1873                    done = True
1874                elif m.try_premirror(ud, self.d):
1875                    logger.debug("Trying PREMIRRORS")
1876                    mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
1877                    done = m.try_mirrors(self, ud, self.d, mirrors)
1878                    if done:
1879                        try:
1880                            # early checksum verification so that if the checksum of the premirror
1881                            # contents mismatch the fetcher can still try upstream and mirrors
1882                            m.update_donestamp(ud, self.d)
1883                        except ChecksumError as e:
1884                            logger.warning("Checksum failure encountered with premirror download of %s - will attempt other sources." % u)
1885                            logger.debug(str(e))
1886                            done = False
1887
1888                d = self.d
1889                if premirroronly:
1890                    # Only disable the network in a copy
1891                    d = bb.data.createCopy(self.d)
1892                    d.setVar("BB_NO_NETWORK", "1")
1893
1894                firsterr = None
1895                verified_stamp = False
1896                if done:
1897                    verified_stamp = m.verify_donestamp(ud, d)
1898                if not done and (not verified_stamp or m.need_update(ud, d)):
1899                    try:
1900                        if not trusted_network(d, ud.url):
1901                            raise UntrustedUrl(ud.url)
1902                        logger.debug("Trying Upstream")
1903                        m.download(ud, d)
1904                        if hasattr(m, "build_mirror_data"):
1905                            m.build_mirror_data(ud, d)
1906                        done = True
1907                        # early checksum verify, so that if checksum mismatched,
1908                        # fetcher still have chance to fetch from mirror
1909                        m.update_donestamp(ud, d)
1910
1911                    except bb.fetch2.NetworkAccess:
1912                        raise
1913
1914                    except BBFetchException as e:
1915                        if isinstance(e, ChecksumError):
1916                            logger.warning("Checksum failure encountered with download of %s - will attempt other sources if available" % u)
1917                            logger.debug(str(e))
1918                            if os.path.exists(ud.localpath):
1919                                rename_bad_checksum(ud, e.checksum)
1920                        elif isinstance(e, NoChecksumError):
1921                            raise
1922                        else:
1923                            logger.warning('Failed to fetch URL %s, attempting MIRRORS if available' % u)
1924                            logger.debug(str(e))
1925                        firsterr = e
1926                        # Remove any incomplete fetch
1927                        if not verified_stamp and m.cleanup_upon_failure():
1928                            m.clean(ud, d)
1929                        logger.debug("Trying MIRRORS")
1930                        mirrors = mirror_from_string(d.getVar('MIRRORS'))
1931                        done = m.try_mirrors(self, ud, d, mirrors)
1932
1933                if not done or not m.done(ud, d):
1934                    if firsterr:
1935                        logger.error(str(firsterr))
1936                    raise FetchError("Unable to fetch URL from any source.", u)
1937
1938                m.update_donestamp(ud, d)
1939
1940            except IOError as e:
1941                if e.errno in [errno.ESTALE]:
1942                    logger.error("Stale Error Observed %s." % u)
1943                    raise ChecksumError("Stale Error Detected")
1944
1945            except BBFetchException as e:
1946                if isinstance(e, NoChecksumError):
1947                    (message, _) = e.args
1948                    checksum_missing_messages.append(message)
1949                    continue
1950                elif isinstance(e, ChecksumError):
1951                    logger.error("Checksum failure fetching %s" % u)
1952                raise
1953
1954            finally:
1955                if ud.lockfile:
1956                    bb.utils.unlockfile(lf)
1957        if checksum_missing_messages:
1958            logger.error("Missing SRC_URI checksum, please add those to the recipe: \n%s", "\n".join(checksum_missing_messages))
1959            raise BBFetchException("There was some missing checksums in the recipe")
1960
1961    def checkstatus(self, urls=None):
1962        """
1963        Check all URLs exist upstream.
1964
1965        Returns None if the URLs exist, raises FetchError if the check wasn't
1966        successful but there wasn't an error (such as file not found), and
1967        raises other exceptions in error cases.
1968        """
1969
1970        if not urls:
1971            urls = self.urls
1972
1973        for u in urls:
1974            ud = self.ud[u]
1975            ud.setup_localpath(self.d)
1976            m = ud.method
1977            logger.debug("Testing URL %s", u)
1978            # First try checking uri, u, from PREMIRRORS
1979            mirrors = mirror_from_string(self.d.getVar('PREMIRRORS'))
1980            ret = m.try_mirrors(self, ud, self.d, mirrors, True)
1981            if not ret:
1982                # Next try checking from the original uri, u
1983                ret = m.checkstatus(self, ud, self.d)
1984                if not ret:
1985                    # Finally, try checking uri, u, from MIRRORS
1986                    mirrors = mirror_from_string(self.d.getVar('MIRRORS'))
1987                    ret = m.try_mirrors(self, ud, self.d, mirrors, True)
1988
1989            if not ret:
1990                raise FetchError("URL doesn't work", u)
1991
1992    def unpack(self, root, urls=None):
1993        """
1994        Unpack urls to root
1995        """
1996
1997        if not urls:
1998            urls = self.urls
1999
2000        unpack_tracer.start(root, self.ud, self.d)
2001
2002        for u in urls:
2003            ud = self.ud[u]
2004            ud.setup_localpath(self.d)
2005
2006            if ud.lockfile:
2007                lf = bb.utils.lockfile(ud.lockfile)
2008
2009            unpack_tracer.start_url(u)
2010            ud.method.unpack(ud, root, self.d)
2011            unpack_tracer.finish_url(u)
2012
2013            if ud.lockfile:
2014                bb.utils.unlockfile(lf)
2015
2016        unpack_tracer.complete()
2017
2018    def clean(self, urls=None):
2019        """
2020        Clean files that the fetcher gets or places
2021        """
2022
2023        if not urls:
2024            urls = self.urls
2025
2026        for url in urls:
2027            if url not in self.ud:
2028                self.ud[url] = FetchData(url, self.d)
2029            ud = self.ud[url]
2030            ud.setup_localpath(self.d)
2031
2032            if not ud.localfile and ud.localpath is None:
2033                continue
2034
2035            if ud.lockfile:
2036                lf = bb.utils.lockfile(ud.lockfile)
2037
2038            ud.method.clean(ud, self.d)
2039            if ud.donestamp:
2040                bb.utils.remove(ud.donestamp)
2041
2042            if ud.lockfile:
2043                bb.utils.unlockfile(lf)
2044
2045    def expanded_urldata(self, urls=None):
2046        """
2047        Get an expanded list of FetchData objects covering both the given
2048        URLS and any additional implicit URLs that are added automatically by
2049        the appropriate FetchMethod.
2050        """
2051
2052        if not urls:
2053            urls = self.urls
2054
2055        urldata = []
2056        for url in urls:
2057            ud = self.ud[url]
2058            urldata.append(ud)
2059            urldata += ud.method.implicit_urldata(ud, self.d)
2060
2061        return urldata
2062
2063class FetchConnectionCache(object):
2064    """
2065        A class which represents an container for socket connections.
2066    """
2067    def __init__(self):
2068        self.cache = {}
2069
2070    def get_connection_name(self, host, port):
2071        return host + ':' + str(port)
2072
2073    def add_connection(self, host, port, connection):
2074        cn = self.get_connection_name(host, port)
2075
2076        if cn not in self.cache:
2077            self.cache[cn] = connection
2078
2079    def get_connection(self, host, port):
2080        connection = None
2081
2082        cn = self.get_connection_name(host, port)
2083        if cn in self.cache:
2084            connection = self.cache[cn]
2085
2086        return connection
2087
2088    def remove_connection(self, host, port):
2089        cn = self.get_connection_name(host, port)
2090        if cn in self.cache:
2091            self.cache[cn].close()
2092            del self.cache[cn]
2093
2094    def close_connections(self):
2095        for cn in list(self.cache.keys()):
2096            self.cache[cn].close()
2097            del self.cache[cn]
2098
2099from . import cvs
2100from . import git
2101from . import gitsm
2102from . import gitannex
2103from . import local
2104from . import svn
2105from . import wget
2106from . import ssh
2107from . import sftp
2108from . import s3
2109from . import perforce
2110from . import bzr
2111from . import hg
2112from . import osc
2113from . import repo
2114from . import clearcase
2115from . import npm
2116from . import npmsw
2117from . import az
2118from . import crate
2119from . import gcp
2120from . import gomod
2121
2122methods.append(local.Local())
2123methods.append(wget.Wget())
2124methods.append(svn.Svn())
2125methods.append(git.Git())
2126methods.append(gitsm.GitSM())
2127methods.append(gitannex.GitANNEX())
2128methods.append(cvs.Cvs())
2129methods.append(ssh.SSH())
2130methods.append(sftp.SFTP())
2131methods.append(s3.S3())
2132methods.append(perforce.Perforce())
2133methods.append(bzr.Bzr())
2134methods.append(hg.Hg())
2135methods.append(osc.Osc())
2136methods.append(repo.Repo())
2137methods.append(clearcase.ClearCase())
2138methods.append(npm.Npm())
2139methods.append(npmsw.NpmShrinkWrap())
2140methods.append(az.Az())
2141methods.append(crate.Crate())
2142methods.append(gcp.GCP())
2143methods.append(gomod.GoMod())
2144methods.append(gomod.GoModGit())
2145