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