fetch2: improve error output for checksum failures
[bitbake.git] / lib / bb / fetch2 / __init__.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 """
4 BitBake 'Fetch' implementations
5
6 Classes for obtaining upstream sources for the
7 BitBake build tools.
8 """
9
10 # Copyright (C) 2003, 2004  Chris Larson
11 # Copyright (C) 2012  Intel Corporation
12 #
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License version 2 as
15 # published by the Free Software Foundation.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License along
23 # with this program; if not, write to the Free Software Foundation, Inc.,
24 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #
26 # Based on functions from the base bb module, Copyright 2003 Holger Schurig
27
28 from __future__ import absolute_import
29 from __future__ import print_function
30 import os, re
31 import logging
32 import urllib
33 import bb.persist_data, bb.utils
34 import bb.checksum
35 from bb import data
36
37 __version__ = "2"
38 _checksum_cache = bb.checksum.FileChecksumCache()
39
40 logger = logging.getLogger("BitBake.Fetcher")
41
42 class BBFetchException(Exception):
43     """Class all fetch exceptions inherit from"""
44     def __init__(self, message):
45          self.msg = message
46          Exception.__init__(self, message)
47
48     def __str__(self):
49          return self.msg
50
51 class MalformedUrl(BBFetchException):
52     """Exception raised when encountering an invalid url"""
53     def __init__(self, url):
54          msg = "The URL: '%s' is invalid and cannot be interpreted" % url
55          self.url = url
56          BBFetchException.__init__(self, msg)
57          self.args = (url,)
58
59 class FetchError(BBFetchException):
60     """General fetcher exception when something happens incorrectly"""
61     def __init__(self, message, url = None):
62          if url:
63             msg = "Fetcher failure for URL: '%s'. %s" % (url, message)
64          else:
65             msg = "Fetcher failure: %s" % message
66          self.url = url
67          BBFetchException.__init__(self, msg)
68          self.args = (message, url)
69
70 class ChecksumError(FetchError):
71     """Exception when mismatched checksum encountered"""
72
73 class NoChecksumError(FetchError):
74     """Exception when no checksum is specified, but BB_STRICT_CHECKSUM is set"""
75
76 class UnpackError(BBFetchException):
77     """General fetcher exception when something happens incorrectly when unpacking"""
78     def __init__(self, message, url):
79          msg = "Unpack failure for URL: '%s'. %s" % (url, message)
80          self.url = url
81          BBFetchException.__init__(self, msg)
82          self.args = (message, url)
83
84 class NoMethodError(BBFetchException):
85     """Exception raised when there is no method to obtain a supplied url or set of urls"""
86     def __init__(self, url):
87          msg = "Could not find a fetcher which supports the URL: '%s'" % url
88          self.url = url
89          BBFetchException.__init__(self, msg)
90          self.args = (url,)
91
92 class MissingParameterError(BBFetchException):
93     """Exception raised when a fetch method is missing a critical parameter in the url"""
94     def __init__(self, missing, url):
95          msg = "URL: '%s' is missing the required parameter '%s'" % (url, missing)
96          self.url = url
97          self.missing = missing
98          BBFetchException.__init__(self, msg)
99          self.args = (missing, url)
100
101 class ParameterError(BBFetchException):
102     """Exception raised when a url cannot be proccessed due to invalid parameters."""
103     def __init__(self, message, url):
104          msg = "URL: '%s' has invalid parameters. %s" % (url, message)
105          self.url = url
106          BBFetchException.__init__(self, msg)
107          self.args = (message, url)
108
109 class NetworkAccess(BBFetchException):
110     """Exception raised when network access is disabled but it is required."""
111     def __init__(self, url, cmd):
112          msg = "Network access disabled through BB_NO_NETWORK but access requested with command %s (for url %s)" % (cmd, url)
113          self.url = url
114          self.cmd = cmd
115          BBFetchException.__init__(self, msg)
116          self.args = (url, cmd)
117
118 class NonLocalMethod(Exception):
119     def __init__(self):
120         Exception.__init__(self)
121
122 def decodeurl(url):
123     """Decodes an URL into the tokens (scheme, network location, path,
124     user, password, parameters).
125     """
126
127     m = re.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
128     if not m:
129         raise MalformedUrl(url)
130
131     type = m.group('type')
132     location = m.group('location')
133     if not location:
134         raise MalformedUrl(url)
135     user = m.group('user')
136     parm = m.group('parm')
137
138     locidx = location.find('/')
139     if locidx != -1 and type.lower() != 'file':
140         host = location[:locidx]
141         path = location[locidx:]
142     else:
143         host = ""
144         path = location
145     if user:
146         m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
147         if m:
148             user = m.group('user')
149             pswd = m.group('pswd')
150     else:
151         user = ''
152         pswd = ''
153
154     p = {}
155     if parm:
156         for s in parm.split(';'):
157             s1, s2 = s.split('=')
158             p[s1] = s2
159
160     return type, host, urllib.unquote(path), user, pswd, p
161
162 def encodeurl(decoded):
163     """Encodes a URL from tokens (scheme, network location, path,
164     user, password, parameters).
165     """
166
167     type, host, path, user, pswd, p = decoded
168
169     if not path:
170         raise MissingParameterError('path', "encoded from the data %s" % str(decoded))
171     if not type:
172         raise MissingParameterError('type', "encoded from the data %s" % str(decoded))
173     url = '%s://' % type
174     if user and type != "file":
175         url += "%s" % user
176         if pswd:
177             url += ":%s" % pswd
178         url += "@"
179     if host and type != "file":
180         url += "%s" % host
181     # Standardise path to ensure comparisons work
182     while '//' in path:
183         path = path.replace("//", "/")
184     url += "%s" % urllib.quote(path)
185     if p:
186         for parm in p:
187             url += ";%s=%s" % (parm, p[parm])
188
189     return url
190
191 def uri_replace(ud, uri_find, uri_replace, replacements, d):
192     if not ud.url or not uri_find or not uri_replace:
193         logger.error("uri_replace: passed an undefined value, not replacing")
194         return None
195     uri_decoded = list(decodeurl(ud.url))
196     uri_find_decoded = list(decodeurl(uri_find))
197     uri_replace_decoded = list(decodeurl(uri_replace))
198     logger.debug(2, "For url %s comparing %s to %s" % (uri_decoded, uri_find_decoded, uri_replace_decoded))
199     result_decoded = ['', '', '', '', '', {}]
200     for loc, i in enumerate(uri_find_decoded):
201         result_decoded[loc] = uri_decoded[loc]
202         regexp = i
203         if loc == 0 and regexp and not regexp.endswith("$"):
204             # Leaving the type unanchored can mean "https" matching "file" can become "files"
205             # which is clearly undesirable.
206             regexp += "$"
207         if loc == 5:
208             # Handle URL parameters
209             if i:
210                 # Any specified URL parameters must match
211                 for k in uri_replace_decoded[loc]:
212                     if uri_decoded[loc][k] != uri_replace_decoded[loc][k]:
213                         return None
214             # Overwrite any specified replacement parameters
215             for k in uri_replace_decoded[loc]:
216                 result_decoded[loc][k] = uri_replace_decoded[loc][k]
217         elif (re.match(regexp, uri_decoded[loc])):
218             if not uri_replace_decoded[loc]:
219                 result_decoded[loc] = ""    
220             else:
221                 for k in replacements:
222                     uri_replace_decoded[loc] = uri_replace_decoded[loc].replace(k, replacements[k])
223                 #bb.note("%s %s %s" % (regexp, uri_replace_decoded[loc], uri_decoded[loc]))
224                 result_decoded[loc] = re.sub(regexp, uri_replace_decoded[loc], uri_decoded[loc])
225             if loc == 2:
226                 # Handle path manipulations
227                 basename = None
228                 if uri_decoded[0] != uri_replace_decoded[0] and ud.mirrortarball:
229                     # If the source and destination url types differ, must be a mirrortarball mapping
230                     basename = os.path.basename(ud.mirrortarball)
231                     # Kill parameters, they make no sense for mirror tarballs
232                     uri_decoded[5] = {}
233                 elif ud.localpath and ud.method.supports_checksum(ud):
234                     basename = os.path.basename(ud.localpath)
235                 if basename and not result_decoded[loc].endswith(basename):
236                     result_decoded[loc] = os.path.join(result_decoded[loc], basename)
237         else:
238             return None
239     result = encodeurl(result_decoded)
240     if result == ud.url:
241         return None
242     logger.debug(2, "For url %s returning %s" % (ud.url, result))
243     return result
244
245 methods = []
246 urldata_cache = {}
247 saved_headrevs = {}
248
249 def fetcher_init(d):
250     """
251     Called to initialize the fetchers once the configuration data is known.
252     Calls before this must not hit the cache.
253     """
254     # When to drop SCM head revisions controlled by user policy
255     srcrev_policy = d.getVar('BB_SRCREV_POLICY', True) or "clear"
256     if srcrev_policy == "cache":
257         logger.debug(1, "Keeping SRCREV cache due to cache policy of: %s", srcrev_policy)
258     elif srcrev_policy == "clear":
259         logger.debug(1, "Clearing SRCREV cache due to cache policy of: %s", srcrev_policy)
260         revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
261         try:
262             bb.fetch2.saved_headrevs = revs.items()
263         except:
264             pass
265         revs.clear()
266     else:
267         raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy)
268
269     _checksum_cache.init_cache(d)
270
271     for m in methods:
272         if hasattr(m, "init"):
273             m.init(d)
274
275 def fetcher_parse_save(d):
276     _checksum_cache.save_extras(d)
277
278 def fetcher_parse_done(d):
279     _checksum_cache.save_merge(d)
280
281 def fetcher_compare_revisions(d):
282     """
283     Compare the revisions in the persistant cache with current values and
284     return true/false on whether they've changed.
285     """
286
287     data = bb.persist_data.persist('BB_URI_HEADREVS', d).items()
288     data2 = bb.fetch2.saved_headrevs
289
290     changed = False
291     for key in data:
292         if key not in data2 or data2[key] != data[key]:
293             logger.debug(1, "%s changed", key)
294             changed = True
295             return True
296         else:
297             logger.debug(2, "%s did not change", key)
298     return False
299
300 def mirror_from_string(data):
301     return [ i.split() for i in (data or "").replace('\\n','\n').split('\n') if i ]
302
303 def verify_checksum(u, ud, d):
304     """
305     verify the MD5 and SHA256 checksum for downloaded src
306
307     Raises a FetchError if one or both of the SRC_URI checksums do not match
308     the downloaded file, or if BB_STRICT_CHECKSUM is set and there are no
309     checksums specified.
310
311     """
312
313     if not ud.method.supports_checksum(ud):
314         return
315
316     md5data = bb.utils.md5_file(ud.localpath)
317     sha256data = bb.utils.sha256_file(ud.localpath)
318
319     if ud.method.recommends_checksum(ud):
320         # If strict checking enabled and neither sum defined, raise error
321         strict = d.getVar("BB_STRICT_CHECKSUM", True) or None
322         if (strict and ud.md5_expected == None and ud.sha256_expected == None):
323             raise NoChecksumError('No checksum specified for %s, please add at least one to the recipe:\n'
324                              'SRC_URI[%s] = "%s"\nSRC_URI[%s] = "%s"' %
325                              (ud.localpath, ud.md5_name, md5data,
326                               ud.sha256_name, sha256data), u)
327
328         # Log missing sums so user can more easily add them
329         if ud.md5_expected == None:
330             logger.warn('Missing md5 SRC_URI checksum for %s, consider adding to the recipe:\n'
331                         'SRC_URI[%s] = "%s"',
332                         ud.localpath, ud.md5_name, md5data)
333
334         if ud.sha256_expected == None:
335             logger.warn('Missing sha256 SRC_URI checksum for %s, consider adding to the recipe:\n'
336                         'SRC_URI[%s] = "%s"',
337                         ud.localpath, ud.sha256_name, sha256data)
338
339     md5mismatch = False
340     sha256mismatch = False
341
342     if ud.md5_expected != md5data:
343         md5mismatch = True
344
345     if ud.sha256_expected != sha256data:
346         sha256mismatch = True
347
348     # We want to alert the user if a checksum is defined in the recipe but
349     # it does not match.
350     msg = ""
351     mismatch = False
352     if md5mismatch and ud.md5_expected:
353         msg = msg + "\nFile: '%s' has %s checksum %s when %s was expected" % (ud.localpath, 'md5', md5data, ud.md5_expected)
354         mismatch = True;
355
356     if sha256mismatch and ud.sha256_expected:
357         msg = msg + "\nFile: '%s' has %s checksum %s when %s was expected" % (ud.localpath, 'sha256', sha256data, ud.sha256_expected)
358         mismatch = True;
359
360     if mismatch:
361         msg = msg + '\nIf this change is expected (e.g. you have upgraded to a new version without updating the checksums) then you can use these lines within the recipe:\nSRC_URI[%s] = "%s"\nSRC_URI[%s] = "%s"\nOtherwise you should retry the download and/or check with upstream to determine if the file has become corrupted or otherwise unexpectedly modified.\n' % (ud.md5_name, md5data, ud.sha256_name, sha256data)
362
363     if len(msg):
364         raise ChecksumError('Checksum mismatch!%s' % msg, u)
365
366
367 def update_stamp(u, ud, d):
368     """
369         donestamp is file stamp indicating the whole fetching is done
370         this function update the stamp after verifying the checksum
371     """
372     if os.path.exists(ud.donestamp):
373         # Touch the done stamp file to show active use of the download
374         try:
375             os.utime(ud.donestamp, None)
376         except:
377             # Errors aren't fatal here
378             pass
379     else:
380         verify_checksum(u, ud, d)
381         open(ud.donestamp, 'w').close()
382
383 def subprocess_setup():
384     import signal
385     # Python installs a SIGPIPE handler by default. This is usually not what
386     # non-Python subprocesses expect.
387     # SIGPIPE errors are known issues with gzip/bash
388     signal.signal(signal.SIGPIPE, signal.SIG_DFL)
389
390 def get_autorev(d):
391     #  only not cache src rev in autorev case
392     if d.getVar('BB_SRCREV_POLICY', True) != "cache":
393         d.setVar('__BB_DONT_CACHE', '1')
394     return "AUTOINC"
395
396 def get_srcrev(d):
397     """
398     Return the version string for the current package
399     (usually to be used as PV)
400     Most packages usually only have one SCM so we just pass on the call.
401     In the multi SCM case, we build a value based on SRCREV_FORMAT which must
402     have been set.
403     """
404
405     scms = []
406     fetcher = Fetch(d.getVar('SRC_URI', True).split(), d)
407     urldata = fetcher.ud
408     for u in urldata:
409         if urldata[u].method.supports_srcrev():
410             scms.append(u)
411
412     if len(scms) == 0:
413         raise FetchError("SRCREV was used yet no valid SCM was found in SRC_URI")
414
415     if len(scms) == 1 and len(urldata[scms[0]].names) == 1:
416         return urldata[scms[0]].method.sortable_revision(scms[0], urldata[scms[0]], d, urldata[scms[0]].names[0])
417
418     #
419     # Mutiple SCMs are in SRC_URI so we resort to SRCREV_FORMAT
420     #
421     format = d.getVar('SRCREV_FORMAT', True)
422     if not format:
423         raise FetchError("The SRCREV_FORMAT variable must be set when multiple SCMs are used.")
424
425     for scm in scms:
426         ud = urldata[scm]
427         for name in ud.names:
428             rev = ud.method.sortable_revision(scm, ud, d, name)
429             format = format.replace(name, rev)
430
431     return format
432
433 def localpath(url, d):
434     fetcher = bb.fetch2.Fetch([url], d)
435     return fetcher.localpath(url)
436
437 def runfetchcmd(cmd, d, quiet = False, cleanup = []):
438     """
439     Run cmd returning the command output
440     Raise an error if interrupted or cmd fails
441     Optionally echo command output to stdout
442     Optionally remove the files/directories listed in cleanup upon failure
443     """
444
445     import bb.process
446     import subprocess
447
448     # Need to export PATH as binary could be in metadata paths
449     # rather than host provided
450     # Also include some other variables.
451     # FIXME: Should really include all export varaiables?
452     exportvars = ['PATH', 'GIT_PROXY_COMMAND', 'GIT_PROXY_HOST',
453                   'GIT_PROXY_PORT', 'GIT_CONFIG', 'http_proxy', 'ftp_proxy',
454                   'https_proxy', 'no_proxy', 'ALL_PROXY', 'all_proxy',
455                   'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'HOME',
456                   'GIT_PROXY_IGNORE', 'SOCKS5_USER', 'SOCKS5_PASSWD']
457
458     for var in exportvars:
459         val = d.getVar(var, True)
460         if val:
461             cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd)
462
463     logger.debug(1, "Running %s", cmd)
464
465     success = False
466     error_message = ""
467
468     try:
469         (output, errors) = bb.process.run(cmd, shell=True, stderr=subprocess.PIPE)
470         success = True
471     except bb.process.NotFoundError as e:
472         error_message = "Fetch command %s" % (e.command)
473     except bb.process.ExecutionError as e:
474         if e.stdout:
475             output = "output:\n%s\n%s" % (e.stdout, e.stderr)
476         elif e.stderr:
477             output = "output:\n%s" % e.stderr
478         else:
479             output = "no output"
480         error_message = "Fetch command failed with exit code %s, %s" % (e.exitcode, output)
481     except bb.process.CmdError as e:
482         error_message = "Fetch command %s could not be run:\n%s" % (e.command, e.msg)
483     if not success:
484         for f in cleanup:
485             try:
486                 bb.utils.remove(f, True)
487             except OSError:
488                 pass
489
490         raise FetchError(error_message)
491
492     return output
493
494 def check_network_access(d, info = "", url = None):
495     """
496     log remote network access, and error if BB_NO_NETWORK is set
497     """
498     if d.getVar("BB_NO_NETWORK", True) == "1":
499         raise NetworkAccess(url, info)
500     else:
501         logger.debug(1, "Fetcher accessed the network with the command %s" % info)
502
503 def build_mirroruris(origud, mirrors, ld):
504     uris = []
505     uds = []
506
507     replacements = {}
508     replacements["TYPE"] = origud.type
509     replacements["HOST"] = origud.host
510     replacements["PATH"] = origud.path
511     replacements["BASENAME"] = origud.path.split("/")[-1]
512     replacements["MIRRORNAME"] = origud.host.replace(':','.') + origud.path.replace('/', '.').replace('*', '.')
513
514     def adduri(uri, ud, uris, uds):
515         for line in mirrors:
516             try:
517                 (find, replace) = line
518             except ValueError:
519                 continue
520             newuri = uri_replace(ud, find, replace, replacements, ld)
521             if not newuri or newuri in uris or newuri == origud.url:
522                 continue
523             try:
524                 newud = FetchData(newuri, ld)
525                 newud.setup_localpath(ld)
526             except bb.fetch2.BBFetchException as e:
527                 logger.debug(1, "Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
528                 logger.debug(1, str(e))
529                 try:
530                     ud.method.clean(ud, ld)
531                 except UnboundLocalError:
532                     pass
533                 continue   
534             uris.append(newuri)
535             uds.append(newud)
536
537             adduri(newuri, newud, uris, uds)
538
539     adduri(None, origud, uris, uds)
540
541     return uris, uds
542
543 def try_mirror_url(newuri, origud, ud, ld, check = False):
544     # Return of None or a value means we're finished
545     # False means try another url
546     try:
547         if check:
548             found = ud.method.checkstatus(newuri, ud, ld)
549             if found:
550                 return found
551             return False
552
553         os.chdir(ld.getVar("DL_DIR", True))
554
555         if not os.path.exists(ud.donestamp) or ud.method.need_update(newuri, ud, ld):
556             ud.method.download(newuri, ud, ld)
557             if hasattr(ud.method,"build_mirror_data"):
558                 ud.method.build_mirror_data(newuri, ud, ld)
559
560         if not ud.localpath or not os.path.exists(ud.localpath):
561             return False
562
563         if ud.localpath == origud.localpath:
564             return ud.localpath
565
566         # We may be obtaining a mirror tarball which needs further processing by the real fetcher
567         # If that tarball is a local file:// we need to provide a symlink to it
568         dldir = ld.getVar("DL_DIR", True)
569         if origud.mirrortarball and os.path.basename(ud.localpath) == os.path.basename(origud.mirrortarball) \
570                 and os.path.basename(ud.localpath) != os.path.basename(origud.localpath):
571             open(ud.donestamp, 'w').close()
572             dest = os.path.join(dldir, os.path.basename(ud.localpath))
573             if not os.path.exists(dest):
574                 os.symlink(ud.localpath, dest)
575             return None
576         # Otherwise the result is a local file:// and we symlink to it
577         if not os.path.exists(origud.localpath):
578             if os.path.islink(origud.localpath):
579                 # Broken symbolic link
580                 os.unlink(origud.localpath)
581
582             os.symlink(ud.localpath, origud.localpath)
583         update_stamp(newuri, origud, ld)
584         return ud.localpath
585
586     except bb.fetch2.NetworkAccess:
587         raise
588
589     except bb.fetch2.BBFetchException as e:
590         if isinstance(e, ChecksumError):
591             logger.warn("Mirror checksum failure for url %s (original url: %s)\nCleaning and trying again." % (newuri, origud.url))
592             logger.warn(str(e))
593         elif isinstance(e, NoChecksumError):
594             raise
595         else:
596             logger.debug(1, "Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
597             logger.debug(1, str(e))
598         try:
599             ud.method.clean(ud, ld)
600         except UnboundLocalError:
601             pass
602         return False
603
604 def try_mirrors(d, origud, mirrors, check = False):
605     """
606     Try to use a mirrored version of the sources.
607     This method will be automatically called before the fetchers go.
608
609     d Is a bb.data instance
610     uri is the original uri we're trying to download
611     mirrors is the list of mirrors we're going to try
612     """
613     ld = d.createCopy()
614
615     uris, uds = build_mirroruris(origud, mirrors, ld)
616
617     for index, uri in enumerate(uris):
618         ret = try_mirror_url(uri, origud, uds[index], ld, check)
619         if ret != False:
620             return ret
621     return None
622
623 def srcrev_internal_helper(ud, d, name):
624     """
625     Return:
626         a) a source revision if specified
627         b) latest revision if SRCREV="AUTOINC"
628         c) None if not specified
629     """
630
631     if 'rev' in ud.parm:
632         return ud.parm['rev']
633
634     if 'tag' in ud.parm:
635         return ud.parm['tag']
636
637     rev = None
638     pn = d.getVar("PN", True)
639     if name != '':
640         rev = d.getVar("SRCREV_%s_pn-%s" % (name, pn), True)
641         if not rev:
642             rev = d.getVar("SRCREV_%s" % name, True)
643     if not rev:
644        rev = d.getVar("SRCREV_pn-%s" % pn, True)
645     if not rev:
646         rev = d.getVar("SRCREV", True)
647     if rev == "INVALID":
648         raise FetchError("Please set SRCREV to a valid value", ud.url)
649     if rev == "AUTOINC":
650         rev = ud.method.latest_revision(ud.url, ud, d, name)
651
652     return rev
653
654
655 def get_checksum_file_list(d):
656     """ Get a list of files checksum in SRC_URI
657
658     Returns the resolved local paths of all local file entries in
659     SRC_URI as a space-separated string
660     """
661     fetch = Fetch([], d, cache = False, localonly = True)
662
663     dl_dir = d.getVar('DL_DIR', True)
664     filelist = []
665     for u in fetch.urls:
666         ud = fetch.ud[u]
667
668         if ud and isinstance(ud.method, local.Local):
669             ud.setup_localpath(d)
670             f = ud.localpath
671             if f.startswith(dl_dir):
672                 # The local fetcher's behaviour is to return a path under DL_DIR if it couldn't find the file anywhere else
673                 if os.path.exists(f):
674                     bb.warn("Getting checksum for %s SRC_URI entry %s: file not found except in DL_DIR" % (d.getVar('PN', True), os.path.basename(f)))
675                 else:
676                     bb.warn("Unable to get checksum for %s SRC_URI entry %s: file could not be found" % (d.getVar('PN', True), os.path.basename(f)))
677                     continue
678             filelist.append(f)
679
680     return " ".join(filelist)
681
682
683 def get_file_checksums(filelist, pn):
684     """Get a list of the checksums for a list of local files
685
686     Returns the checksums for a list of local files, caching the results as
687     it proceeds
688
689     """
690
691     def checksum_file(f):
692         try:
693             checksum = _checksum_cache.get_checksum(f)
694         except OSError as e:
695             import traceback
696             bb.warn("Unable to get checksum for %s SRC_URI entry %s: %s" % (pn, os.path.basename(f), e))
697             return None
698         return checksum
699
700     checksums = []
701     for pth in filelist.split():
702         checksum = None
703         if '*' in pth:
704             # Handle globs
705             import glob
706             for f in glob.glob(pth):
707                 checksum = checksum_file(f)
708                 if checksum:
709                     checksums.append((f, checksum))
710         elif os.path.isdir(pth):
711             # Handle directories
712             for root, dirs, files in os.walk(pth):
713                 for name in files:
714                     fullpth = os.path.join(root, name)
715                     checksum = checksum_file(fullpth)
716                     if checksum:
717                         checksums.append((fullpth, checksum))
718         else:
719             checksum = checksum_file(pth)
720
721         if checksum:
722             checksums.append((pth, checksum))
723
724     checksums.sort()
725     return checksums
726
727
728 class FetchData(object):
729     """
730     A class which represents the fetcher state for a given URI.
731     """
732     def __init__(self, url, d, localonly = False):
733         # localpath is the location of a downloaded result. If not set, the file is local.
734         self.donestamp = None
735         self.localfile = ""
736         self.localpath = None
737         self.lockfile = None
738         self.mirrortarball = None
739         self.basename = None
740         (self.type, self.host, self.path, self.user, self.pswd, self.parm) = decodeurl(data.expand(url, d))
741         self.date = self.getSRCDate(d)
742         self.url = url
743         if not self.user and "user" in self.parm:
744             self.user = self.parm["user"]
745         if not self.pswd and "pswd" in self.parm:
746             self.pswd = self.parm["pswd"]
747         self.setup = False
748
749         if "name" in self.parm:
750             self.md5_name = "%s.md5sum" % self.parm["name"]
751             self.sha256_name = "%s.sha256sum" % self.parm["name"]
752         else:
753             self.md5_name = "md5sum"
754             self.sha256_name = "sha256sum"
755         if self.md5_name in self.parm:
756             self.md5_expected = self.parm[self.md5_name]
757         elif self.type not in ["http", "https", "ftp", "ftps"]:
758             self.md5_expected = None
759         else:
760             self.md5_expected = d.getVarFlag("SRC_URI", self.md5_name)
761         if self.sha256_name in self.parm:
762             self.sha256_expected = self.parm[self.sha256_name]
763         elif self.type not in ["http", "https", "ftp", "ftps"]:
764             self.sha256_expected = None
765         else:
766             self.sha256_expected = d.getVarFlag("SRC_URI", self.sha256_name)
767
768         self.names = self.parm.get("name",'default').split(',')
769
770         self.method = None
771         for m in methods:
772             if m.supports(url, self, d):
773                 self.method = m
774                 break                
775
776         if not self.method:
777             raise NoMethodError(url)
778
779         if localonly and not isinstance(self.method, local.Local):
780             raise NonLocalMethod()
781
782         if self.parm.get("proto", None) and "protocol" not in self.parm:
783             logger.warn('Consider updating %s recipe to use "protocol" not "proto" in SRC_URI.', d.getVar('PN', True))
784             self.parm["protocol"] = self.parm.get("proto", None)
785
786         if hasattr(self.method, "urldata_init"):
787             self.method.urldata_init(self, d)
788
789         if "localpath" in self.parm:
790             # if user sets localpath for file, use it instead.
791             self.localpath = self.parm["localpath"]
792             self.basename = os.path.basename(self.localpath)
793         elif self.localfile:
794             self.localpath = self.method.localpath(self.url, self, d)
795
796         # Note: These files should always be in DL_DIR whereas localpath may not be.
797         basepath = d.expand("${DL_DIR}/%s" % os.path.basename(self.localpath or self.basename))
798         self.donestamp = basepath + '.done'
799         self.lockfile = basepath + '.lock'
800
801     def setup_revisons(self, d):
802         self.revisions = {}
803         for name in self.names:
804             self.revisions[name] = srcrev_internal_helper(self, d, name)
805
806         # add compatibility code for non name specified case
807         if len(self.names) == 1:
808             self.revision = self.revisions[self.names[0]]
809
810     def setup_localpath(self, d):
811         if not self.localpath:
812             self.localpath = self.method.localpath(self.url, self, d)
813
814     def getSRCDate(self, d):
815         """
816         Return the SRC Date for the component
817
818         d the bb.data module
819         """
820         if "srcdate" in self.parm:
821             return self.parm['srcdate']
822
823         pn = d.getVar("PN", True)
824
825         if pn:
826             return d.getVar("SRCDATE_%s" % pn, True) or d.getVar("SRCDATE", True) or d.getVar("DATE", True)
827
828         return d.getVar("SRCDATE", True) or d.getVar("DATE", True)
829
830 class FetchMethod(object):
831     """Base class for 'fetch'ing data"""
832
833     def __init__(self, urls = []):
834         self.urls = []
835
836     def supports(self, url, urldata, d):
837         """
838         Check to see if this fetch class supports a given url.
839         """
840         return 0
841
842     def localpath(self, url, urldata, d):
843         """
844         Return the local filename of a given url assuming a successful fetch.
845         Can also setup variables in urldata for use in go (saving code duplication
846         and duplicate code execution)
847         """
848         return os.path.join(data.getVar("DL_DIR", d, True), urldata.localfile)
849
850     def supports_checksum(self, urldata):
851         """
852         Is localpath something that can be represented by a checksum?
853         """
854
855         # We cannot compute checksums for directories
856         if os.path.isdir(urldata.localpath) == True:
857             return False
858         if urldata.localpath.find("*") != -1:
859              return False
860
861         return True
862
863     def recommends_checksum(self, urldata):
864         """
865         Is the backend on where checksumming is recommended (should warnings 
866         by displayed if there is no checksum)?
867         """
868         return False
869
870     def _strip_leading_slashes(self, relpath):
871         """
872         Remove leading slash as os.path.join can't cope
873         """
874         while os.path.isabs(relpath):
875             relpath = relpath[1:]
876         return relpath
877
878     def setUrls(self, urls):
879         self.__urls = urls
880
881     def getUrls(self):
882         return self.__urls
883
884     urls = property(getUrls, setUrls, None, "Urls property")
885
886     def need_update(self, url, ud, d):
887         """
888         Force a fetch, even if localpath exists?
889         """
890         if os.path.exists(ud.localpath):
891             return False
892         return True
893
894     def supports_srcrev(self):
895         """
896         The fetcher supports auto source revisions (SRCREV)
897         """
898         return False
899
900     def download(self, url, urldata, d):
901         """
902         Fetch urls
903         Assumes localpath was called first
904         """
905         raise NoMethodError(url)
906
907     def unpack(self, urldata, rootdir, data):
908         import subprocess
909         iterate = False
910         file = urldata.localpath
911
912         try:
913             unpack = bb.utils.to_boolean(urldata.parm.get('unpack'), True)
914         except ValueError as exc:
915             bb.fatal("Invalid value for 'unpack' parameter for %s: %s" %
916                      (file, urldata.parm.get('unpack')))
917
918         dots = file.split(".")
919         if dots[-1] in ['gz', 'bz2', 'Z']:
920             efile = os.path.join(rootdir, os.path.basename('.'.join(dots[0:-1])))
921         else:
922             efile = file
923         cmd = None
924
925         if unpack:
926             if file.endswith('.tar'):
927                 cmd = 'tar x --no-same-owner -f %s' % file
928             elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
929                 cmd = 'tar xz --no-same-owner -f %s' % file
930             elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
931                 cmd = 'bzip2 -dc %s | tar x --no-same-owner -f -' % file
932             elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
933                 cmd = 'gzip -dc %s > %s' % (file, efile)
934             elif file.endswith('.bz2'):
935                 cmd = 'bzip2 -dc %s > %s' % (file, efile)
936             elif file.endswith('.tar.xz'):
937                 cmd = 'xz -dc %s | tar x --no-same-owner -f -' % file
938             elif file.endswith('.xz'):
939                 cmd = 'xz -dc %s > %s' % (file, efile)
940             elif file.endswith('.zip') or file.endswith('.jar'):
941                 try:
942                     dos = bb.utils.to_boolean(urldata.parm.get('dos'), False)
943                 except ValueError as exc:
944                     bb.fatal("Invalid value for 'dos' parameter for %s: %s" %
945                              (file, urldata.parm.get('dos')))
946                 cmd = 'unzip -q -o'
947                 if dos:
948                     cmd = '%s -a' % cmd
949                 cmd = "%s '%s'" % (cmd, file)
950             elif file.endswith('.rpm') or file.endswith('.srpm'):
951                 if 'extract' in urldata.parm:
952                     unpack_file = urldata.parm.get('extract')
953                     cmd = 'rpm2cpio.sh %s | cpio -i %s' % (file, unpack_file)
954                     iterate = True
955                     iterate_file = unpack_file
956                 else:
957                     cmd = 'rpm2cpio.sh %s | cpio -i' % (file)
958             elif file.endswith('.deb') or file.endswith('.ipk'):
959                 cmd = 'ar -p %s data.tar.gz | zcat | tar --no-same-owner -xpf -' % file
960
961         if not unpack or not cmd:
962             # If file == dest, then avoid any copies, as we already put the file into dest!
963             dest = os.path.join(rootdir, os.path.basename(file))
964             if (file != dest) and not (os.path.exists(dest) and os.path.samefile(file, dest)):
965                 if os.path.isdir(file):
966                     filesdir = os.path.realpath(data.getVar("FILESDIR", True))
967                     destdir = "."
968                     if file[0:len(filesdir)] == filesdir:
969                         destdir = file[len(filesdir):file.rfind('/')]
970                         destdir = destdir.strip('/')
971                         if len(destdir) < 1:
972                             destdir = "."
973                         elif not os.access("%s/%s" % (rootdir, destdir), os.F_OK):
974                             os.makedirs("%s/%s" % (rootdir, destdir))
975                     cmd = 'cp -pPR %s %s/%s/' % (file, rootdir, destdir)
976                     #cmd = 'tar -cf - -C "%d" -ps . | tar -xf - -C "%s/%s/"' % (file, rootdir, destdir)
977                 else:
978                     # The "destdir" handling was specifically done for FILESPATH
979                     # items.  So, only do so for file:// entries.
980                     if urldata.type == "file" and urldata.path.find("/") != -1:
981                        destdir = urldata.path.rsplit("/", 1)[0]
982                     else:
983                        destdir = "."
984                     bb.utils.mkdirhier("%s/%s" % (rootdir, destdir))
985                     cmd = 'cp %s %s/%s/' % (file, rootdir, destdir)
986
987         if not cmd:
988             return
989
990         # Change to subdir before executing command
991         save_cwd = os.getcwd();
992         os.chdir(rootdir)
993         if 'subdir' in urldata.parm:
994             newdir = ("%s/%s" % (rootdir, urldata.parm.get('subdir')))
995             bb.utils.mkdirhier(newdir)
996             os.chdir(newdir)
997
998         path = data.getVar('PATH', True)
999         if path:
1000             cmd = "PATH=\"%s\" %s" % (path, cmd)
1001         bb.note("Unpacking %s to %s/" % (file, os.getcwd()))
1002         ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True)
1003
1004         os.chdir(save_cwd)
1005
1006         if ret != 0:
1007             raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), urldata.url)
1008
1009         if iterate is True:
1010             iterate_urldata = urldata
1011             iterate_urldata.localpath = "%s/%s" % (rootdir, iterate_file)
1012             self.unpack(urldata, rootdir, data)
1013
1014         return
1015
1016     def clean(self, urldata, d):
1017        """
1018        Clean any existing full or partial download
1019        """
1020        bb.utils.remove(urldata.localpath)
1021
1022     def try_premirror(self, url, urldata, d):
1023         """
1024         Should premirrors be used?
1025         """
1026         return True
1027
1028     def checkstatus(self, url, urldata, d):
1029         """
1030         Check the status of a URL
1031         Assumes localpath was called first
1032         """
1033         logger.info("URL %s could not be checked for status since no method exists.", url)
1034         return True
1035
1036     def localcount_internal_helper(ud, d, name):
1037         """
1038         Return:
1039             a) a locked localcount if specified
1040             b) None otherwise
1041         """
1042
1043         localcount = None
1044         if name != '':
1045             pn = d.getVar("PN", True)
1046             localcount = d.getVar("LOCALCOUNT_" + name, True)
1047         if not localcount:
1048             localcount = d.getVar("LOCALCOUNT", True)
1049         return localcount
1050
1051     localcount_internal_helper = staticmethod(localcount_internal_helper)
1052
1053     def latest_revision(self, url, ud, d, name):
1054         """
1055         Look in the cache for the latest revision, if not present ask the SCM.
1056         """
1057         if not hasattr(self, "_latest_revision"):
1058             raise ParameterError("The fetcher for this URL does not support _latest_revision", url)
1059
1060         revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
1061         key = self.generate_revision_key(url, ud, d, name)
1062         try:
1063             return revs[key]
1064         except KeyError:
1065             revs[key] = rev = self._latest_revision(url, ud, d, name)
1066             return rev
1067
1068     def sortable_revision(self, url, ud, d, name):
1069         """
1070
1071         """
1072         if hasattr(self, "_sortable_revision"):
1073             return self._sortable_revision(url, ud, d)
1074
1075         localcounts = bb.persist_data.persist('BB_URI_LOCALCOUNT', d)
1076         key = self.generate_revision_key(url, ud, d, name)
1077
1078         latest_rev = self._build_revision(url, ud, d, name)
1079         last_rev = localcounts.get(key + '_rev')
1080         uselocalcount = d.getVar("BB_LOCALCOUNT_OVERRIDE", True) or False
1081         count = None
1082         if uselocalcount:
1083             count = FetchMethod.localcount_internal_helper(ud, d, name)
1084         if count is None:
1085             count = localcounts.get(key + '_count') or "0"
1086
1087         if last_rev == latest_rev:
1088             return str(count + "+" + latest_rev)
1089
1090         buildindex_provided = hasattr(self, "_sortable_buildindex")
1091         if buildindex_provided:
1092             count = self._sortable_buildindex(url, ud, d, latest_rev)
1093
1094         if count is None:
1095             count = "0"
1096         elif uselocalcount or buildindex_provided:
1097             count = str(count)
1098         else:
1099             count = str(int(count) + 1)
1100
1101         localcounts[key + '_rev'] = latest_rev
1102         localcounts[key + '_count'] = count
1103
1104         return str(count + "+" + latest_rev)
1105
1106     def generate_revision_key(self, url, ud, d, name):
1107         key = self._revision_key(url, ud, d, name)
1108         return "%s-%s" % (key, d.getVar("PN", True) or "")
1109
1110 class Fetch(object):
1111     def __init__(self, urls, d, cache = True, localonly = False):
1112         if localonly and cache:
1113             raise Exception("bb.fetch2.Fetch.__init__: cannot set cache and localonly at same time")
1114
1115         if len(urls) == 0:
1116             urls = d.getVar("SRC_URI", True).split()
1117         self.urls = urls
1118         self.d = d
1119         self.ud = {}
1120
1121         fn = d.getVar('FILE', True)
1122         if cache and fn and fn in urldata_cache:
1123             self.ud = urldata_cache[fn]
1124
1125         for url in urls:
1126             if url not in self.ud:
1127                 try:
1128                     self.ud[url] = FetchData(url, d, localonly)
1129                 except NonLocalMethod:
1130                     if localonly:
1131                         self.ud[url] = None
1132                         pass
1133
1134         if fn and cache:
1135             urldata_cache[fn] = self.ud
1136
1137     def localpath(self, url):
1138         if url not in self.urls:
1139             self.ud[url] = FetchData(url, self.d)
1140
1141         self.ud[url].setup_localpath(self.d)
1142         return self.d.expand(self.ud[url].localpath)
1143
1144     def localpaths(self):
1145         """
1146         Return a list of the local filenames, assuming successful fetch
1147         """
1148         local = []
1149
1150         for u in self.urls:
1151             ud = self.ud[u]
1152             ud.setup_localpath(self.d)
1153             local.append(ud.localpath)
1154
1155         return local
1156
1157     def download(self, urls = []):
1158         """
1159         Fetch all urls
1160         """
1161         if len(urls) == 0:
1162             urls = self.urls
1163
1164         network = self.d.getVar("BB_NO_NETWORK", True)
1165         premirroronly = (self.d.getVar("BB_FETCH_PREMIRRORONLY", True) == "1")
1166
1167         for u in urls:
1168             ud = self.ud[u]
1169             ud.setup_localpath(self.d)
1170             m = ud.method
1171             localpath = ""
1172
1173             lf = bb.utils.lockfile(ud.lockfile)
1174
1175             try:
1176                 self.d.setVar("BB_NO_NETWORK", network)
1177  
1178                 if os.path.exists(ud.donestamp) and not m.need_update(u, ud, self.d):
1179                     localpath = ud.localpath
1180                 elif m.try_premirror(u, ud, self.d):
1181                     logger.debug(1, "Trying PREMIRRORS")
1182                     mirrors = mirror_from_string(self.d.getVar('PREMIRRORS', True))
1183                     localpath = try_mirrors(self.d, ud, mirrors, False)
1184
1185                 if premirroronly:
1186                     self.d.setVar("BB_NO_NETWORK", "1")
1187
1188                 os.chdir(self.d.getVar("DL_DIR", True))
1189
1190                 firsterr = None
1191                 if not localpath and ((not os.path.exists(ud.donestamp)) or m.need_update(u, ud, self.d)):
1192                     try:
1193                         logger.debug(1, "Trying Upstream")
1194                         m.download(u, ud, self.d)
1195                         if hasattr(m, "build_mirror_data"):
1196                             m.build_mirror_data(u, ud, self.d)
1197                         localpath = ud.localpath
1198                         # early checksum verify, so that if checksum mismatched,
1199                         # fetcher still have chance to fetch from mirror
1200                         update_stamp(u, ud, self.d)
1201
1202                     except bb.fetch2.NetworkAccess:
1203                         raise
1204
1205                     except BBFetchException as e:
1206                         if isinstance(e, ChecksumError):
1207                             logger.warn("Checksum failure encountered with download of %s - will attempt other sources if available" % u)
1208                             logger.debug(1, str(e))
1209                         elif isinstance(e, NoChecksumError):
1210                             raise
1211                         else:
1212                             logger.warn('Failed to fetch URL %s, attempting MIRRORS if available' % u)
1213                             logger.debug(1, str(e))
1214                         firsterr = e
1215                         # Remove any incomplete fetch
1216                         m.clean(ud, self.d)
1217                         logger.debug(1, "Trying MIRRORS")
1218                         mirrors = mirror_from_string(self.d.getVar('MIRRORS', True))
1219                         localpath = try_mirrors (self.d, ud, mirrors)
1220
1221                 if not localpath or ((not os.path.exists(localpath)) and localpath.find("*") == -1):
1222                     if firsterr:
1223                         logger.error(str(firsterr))
1224                     raise FetchError("Unable to fetch URL from any source.", u)
1225
1226                 update_stamp(u, ud, self.d)
1227
1228             except BBFetchException as e:
1229                 if isinstance(e, NoChecksumError):
1230                     logger.error("%s" % str(e))
1231                 elif isinstance(e, ChecksumError):
1232                     logger.error("Checksum failure fetching %s" % u)
1233                 raise
1234
1235             finally:
1236                 bb.utils.unlockfile(lf)
1237
1238     def checkstatus(self, urls = []):
1239         """
1240         Check all urls exist upstream
1241         """
1242
1243         if len(urls) == 0:
1244             urls = self.urls
1245
1246         for u in urls:
1247             ud = self.ud[u]
1248             ud.setup_localpath(self.d)
1249             m = ud.method
1250             logger.debug(1, "Testing URL %s", u)
1251             # First try checking uri, u, from PREMIRRORS
1252             mirrors = mirror_from_string(self.d.getVar('PREMIRRORS', True))
1253             ret = try_mirrors(self.d, ud, mirrors, True)
1254             if not ret:
1255                 # Next try checking from the original uri, u
1256                 try:
1257                     ret = m.checkstatus(u, ud, self.d)
1258                 except:
1259                     # Finally, try checking uri, u, from MIRRORS
1260                     mirrors = mirror_from_string(self.d.getVar('MIRRORS', True))
1261                     ret = try_mirrors(self.d, ud, mirrors, True)
1262
1263             if not ret:
1264                 raise FetchError("URL %s doesn't work" % u, u)
1265
1266     def unpack(self, root, urls = []):
1267         """
1268         Check all urls exist upstream
1269         """
1270
1271         if len(urls) == 0:
1272             urls = self.urls
1273
1274         for u in urls:
1275             ud = self.ud[u]
1276             ud.setup_localpath(self.d)
1277
1278             if self.d.expand(self.localpath) is None:
1279                 continue
1280
1281             if ud.lockfile:
1282                 lf = bb.utils.lockfile(ud.lockfile)
1283
1284             ud.method.unpack(ud, root, self.d)
1285
1286             if ud.lockfile:
1287                 bb.utils.unlockfile(lf)
1288
1289     def clean(self, urls = []):
1290         """
1291         Clean files that the fetcher gets or places
1292         """
1293
1294         if len(urls) == 0:
1295             urls = self.urls
1296
1297         for url in urls:
1298             if url not in self.ud:
1299                 self.ud[url] = FetchData(url, d)
1300             ud = self.ud[url]
1301             ud.setup_localpath(self.d)
1302
1303             if not ud.localfile or self.localpath is None:
1304                 continue
1305
1306             if ud.lockfile:
1307                 lf = bb.utils.lockfile(ud.lockfile)
1308
1309             ud.method.clean(ud, self.d)
1310             if ud.donestamp:
1311                 bb.utils.remove(ud.donestamp)
1312
1313             if ud.lockfile:
1314                 bb.utils.unlockfile(lf)
1315
1316 from . import cvs
1317 from . import git
1318 from . import local
1319 from . import svn
1320 from . import wget
1321 from . import svk
1322 from . import ssh
1323 from . import perforce
1324 from . import bzr
1325 from . import hg
1326 from . import osc
1327 from . import repo
1328
1329 methods.append(local.Local())
1330 methods.append(wget.Wget())
1331 methods.append(svn.Svn())
1332 methods.append(git.Git())
1333 methods.append(cvs.Cvs())
1334 methods.append(svk.Svk())
1335 methods.append(ssh.SSH())
1336 methods.append(perforce.Perforce())
1337 methods.append(bzr.Bzr())
1338 methods.append(hg.Hg())
1339 methods.append(osc.Osc())
1340 methods.append(repo.Repo())