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