]> git.mxchange.org Git - quix0rs-apt-p2p.git/blob - apt_p2p/AptPackages.py
ef2a1f327c90429b3d1d9d11f6f0e51e9b4e42e0
[quix0rs-apt-p2p.git] / apt_p2p / AptPackages.py
1 #
2 # Copyright (C) 2002 Manuel Estrada Sainz <ranty@debian.org>
3 # Copyright (C) 2008 Cameron Dale <camrdale@gmail.com>
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of version 2.1 of the GNU General Public
7 # License as published by the Free Software Foundation.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
18 """Manage a mirror's index files.
19
20 @type TRACKED_FILES: C{list} of C{string}
21 @var TRACKED_FILES: the file names of files that contain index information
22 """
23
24 # Disable the FutureWarning from the apt module
25 import warnings
26 warnings.simplefilter("ignore", FutureWarning)
27
28 import os, shelve
29 from random import choice
30 from shutil import rmtree
31 from copy import deepcopy
32 from UserDict import DictMixin
33
34 from twisted.internet import threads, defer, reactor
35 from twisted.python import log
36 from twisted.python.filepath import FilePath
37 from twisted.trial import unittest
38
39 import apt_pkg, apt_inst
40 from apt import OpProgress
41 from debian_bundle import deb822
42
43 from apt_p2p_conf import config
44 from Hash import HashObject
45
46 apt_pkg.init()
47
48 TRACKED_FILES = ['release', 'sources', 'packages']
49
50 class PackageFileList(DictMixin):
51     """Manages a list of index files belonging to a mirror.
52     
53     @type cache_dir: L{twisted.python.filepath.FilePath}
54     @ivar cache_dir: the directory to use for storing all files
55     @type packages: C{shelve dictionary}
56     @ivar packages: the files tracked for this mirror
57     """
58     
59     def __init__(self, cache_dir):
60         """Initialize the list by opening the dictionary."""
61         self.cache_dir = cache_dir
62         self.cache_dir.restat(False)
63         if not self.cache_dir.exists():
64             self.cache_dir.makedirs()
65         self.packages = None
66         self.open()
67
68     def open(self):
69         """Open the persistent dictionary of files for this mirror."""
70         if self.packages is None:
71             self.packages = shelve.open(self.cache_dir.child('packages.db').path)
72
73     def close(self):
74         """Close the persistent dictionary."""
75         if self.packages is not None:
76             self.packages.close()
77
78     def update_file(self, cache_path, file_path):
79         """Check if an updated file needs to be tracked.
80
81         Called from the mirror manager when files get updated so we can update our
82         fake lists and sources.list.
83         
84         @type cache_path: C{string}
85         @param cache_path: the location of the file within the mirror
86         @type file_path: L{twisted.python.filepath.FilePath}
87         @param file_path: The location of the file in the file system
88         @rtype: C{boolean}
89         @return: whether the file is an index file
90         """
91         filename = cache_path.split('/')[-1]
92         if filename.lower() in TRACKED_FILES:
93             log.msg("Registering package file: "+cache_path)
94             self.packages[cache_path] = file_path
95             return True
96         return False
97
98     def check_files(self):
99         """Check all files in the database to remove any that don't exist."""
100         files = self.packages.keys()
101         for f in files:
102             self.packages[f].restat(False)
103             if not self.packages[f].exists():
104                 log.msg("File in packages database has been deleted: "+f)
105                 del self.packages[f]
106
107     #{ Dictionary interface details
108     def __getitem__(self, key): return self.packages[key]
109     def __setitem__(self, key, item): self.packages[key] = item
110     def __delitem__(self, key): del self.packages[key]
111     def keys(self): return self.packages.keys()
112
113 class AptPackages:
114     """Answers queries about packages available from a mirror.
115     
116     Uses the python-apt tools to parse and provide information about the
117     files that are available on a single mirror.
118     
119     @ivar DEFAULT_APT_CONFIG: the default configuration parameters to use for apt
120     @ivar essential_dirs: directories that must be created for apt to work
121     @ivar essential_files: files that must be created for apt to work
122     @type cache_dir: L{twisted.python.filepath.FilePath}
123     @ivar cache_dir: the directory to use for storing all files
124     @ivar apt_config: the configuration parameters to use for apt
125     @type packages: L{PackageFileList}
126     @ivar packages: the persistent storage of tracked apt index files
127     @type loaded: C{boolean}
128     @ivar loaded: whether the apt cache is currently loaded
129     @type loading: L{twisted.internet.defer.Deferred}
130     @ivar loading: if the cache is currently being loaded, this will be
131         called when it is loaded, otherwise it is None
132     @type unload_later: L{twisted.internet.interfaces.IDelayedCall}
133     @ivar unload_later: the delayed call to unload the apt cache
134     @type indexrecords: C{dictionary}
135     @ivar indexrecords: the hashes of index files for the mirror, keys are
136         mirror directories, values are dictionaries with keys the path to the
137         index file in the mirror directory and values are dictionaries with
138         keys the hash type and values the hash
139     @type cache: C{apt_pkg.GetCache()}
140     @ivar cache: the apt cache of the mirror
141     @type records: C{apt_pkg.GetPkgRecords()}
142     @ivar records: the apt package records for all binary packages in a mirror
143     @type srcrecords: C{apt_pkg.GetPkgSrcRecords}
144     @ivar srcrecords: the apt package records for all source packages in a mirror
145     """
146
147     DEFAULT_APT_CONFIG = {
148         #'APT' : '',
149         #'APT::Architecture' : 'i386',  # Commented so the machine's config will set this
150         #'APT::Default-Release' : 'unstable',
151         'Dir':'.', # /
152         'Dir::State' : 'apt/', # var/lib/apt/
153         'Dir::State::Lists': 'lists/', # lists/
154         #'Dir::State::cdroms' : 'cdroms.list',
155         'Dir::State::userstatus' : 'status.user',
156         'Dir::State::status': 'dpkg/status', # '/var/lib/dpkg/status'
157         'Dir::Cache' : '.apt/cache/', # var/cache/apt/
158         #'Dir::Cache::archives' : 'archives/',
159         'Dir::Cache::srcpkgcache' : 'srcpkgcache.bin',
160         'Dir::Cache::pkgcache' : 'pkgcache.bin',
161         'Dir::Etc' : 'apt/etc/', # etc/apt/
162         'Dir::Etc::sourcelist' : 'sources.list',
163         'Dir::Etc::vendorlist' : 'vendors.list',
164         'Dir::Etc::vendorparts' : 'vendors.list.d',
165         #'Dir::Etc::main' : 'apt.conf',
166         #'Dir::Etc::parts' : 'apt.conf.d',
167         #'Dir::Etc::preferences' : 'preferences',
168         'Dir::Bin' : '',
169         #'Dir::Bin::methods' : '', #'/usr/lib/apt/methods'
170         'Dir::Bin::dpkg' : '/usr/bin/dpkg',
171         #'DPkg' : '',
172         #'DPkg::Pre-Install-Pkgs' : '',
173         #'DPkg::Tools' : '',
174         #'DPkg::Tools::Options' : '',
175         #'DPkg::Tools::Options::/usr/bin/apt-listchanges' : '',
176         #'DPkg::Tools::Options::/usr/bin/apt-listchanges::Version' : '2',
177         #'DPkg::Post-Invoke' : '',
178         }
179     essential_dirs = ('apt', 'apt/cache', 'apt/dpkg', 'apt/etc', 'apt/lists',
180                       'apt/lists/partial')
181     essential_files = ('apt/dpkg/status', 'apt/etc/sources.list',)
182         
183     def __init__(self, cache_dir):
184         """Construct a new packages manager.
185
186         @param cache_dir: directory to use to store files for this mirror
187         """
188         self.cache_dir = cache_dir
189         self.apt_config = deepcopy(self.DEFAULT_APT_CONFIG)
190
191         # Create the necessary files and directories for apt
192         for dir in self.essential_dirs:
193             path = self.cache_dir.preauthChild(dir)
194             if not path.exists():
195                 path.makedirs()
196         for file in self.essential_files:
197             path = self.cache_dir.preauthChild(file)
198             if not path.exists():
199                 path.touch()
200                 
201         self.apt_config['Dir'] = self.cache_dir.path
202         self.apt_config['Dir::State::status'] = self.cache_dir.preauthChild(self.apt_config['Dir::State']).preauthChild(self.apt_config['Dir::State::status']).path
203         self.packages = PackageFileList(cache_dir)
204         self.loaded = False
205         self.loading = None
206         self.unload_later = None
207         
208     def __del__(self):
209         self.cleanup()
210         
211     def addRelease(self, cache_path, file_path):
212         """Add a Release file's info to the list of index files.
213         
214         Dirty hack until python-apt supports apt-pkg/indexrecords.h
215         (see Bug #456141)
216         """
217         self.indexrecords[cache_path] = {}
218
219         read_packages = False
220         f = file_path.open('r')
221         
222         # Use python-debian routines to parse the file for hashes
223         rel = deb822.Release(f, fields = ['MD5Sum', 'SHA1', 'SHA256'])
224         for hash_type in rel:
225             for file in rel[hash_type]:
226                 self.indexrecords[cache_path].setdefault(file['name'], {})[hash_type.upper()] = (file[hash_type], file['size'])
227             
228         f.close()
229
230     def file_updated(self, cache_path, file_path):
231         """A file in the mirror has changed or been added.
232         
233         If this affects us, unload our apt database.
234         @see: L{PackageFileList.update_file}
235         """
236         if self.packages.update_file(cache_path, file_path):
237             self.unload()
238
239     def load(self):
240         """Make sure the package cache is initialized and loaded."""
241         # Reset the pending unload call
242         if self.unload_later and self.unload_later.active():
243             self.unload_later.reset(config.gettime('DEFAULT', 'UNLOAD_PACKAGES_CACHE'))
244         else:
245             self.unload_later = reactor.callLater(config.gettime('DEFAULT', 'UNLOAD_PACKAGES_CACHE'), self.unload)
246             
247         # Make sure it's not already being loaded
248         if self.loading is None:
249             log.msg('Loading the packages cache')
250             self.loading = threads.deferToThread(self._load)
251             self.loading.addCallback(self.doneLoading)
252         return self.loading
253         
254     def doneLoading(self, loadResult):
255         """Cache is loaded."""
256         self.loading = None
257         # Must pass on the result for the next callback
258         return loadResult
259         
260     def _load(self):
261         """Regenerates the fake configuration and loads the packages caches."""
262         if self.loaded: return True
263         
264         # Modify the default configuration to create the fake one.
265         apt_pkg.InitSystem()
266         self.cache_dir.preauthChild(self.apt_config['Dir::State']
267                      ).preauthChild(self.apt_config['Dir::State::Lists']).remove()
268         self.cache_dir.preauthChild(self.apt_config['Dir::State']
269                      ).preauthChild(self.apt_config['Dir::State::Lists']
270                      ).child('partial').makedirs()
271         sources_file = self.cache_dir.preauthChild(self.apt_config['Dir::Etc']
272                                ).preauthChild(self.apt_config['Dir::Etc::sourcelist'])
273         sources = sources_file.open('w')
274         sources_count = 0
275         deb_src_added = False
276         self.packages.check_files()
277         self.indexrecords = {}
278         
279         # Create an entry in sources.list for each needed index file
280         for f in self.packages:
281             # we should probably clear old entries from self.packages and
282             # take into account the recorded mtime as optimization
283             file = self.packages[f]
284             if f.split('/')[-1] == "Release":
285                 self.addRelease(f, file)
286             fake_uri='http://apt-p2p'+f
287             fake_dirname = '/'.join(fake_uri.split('/')[:-1])
288             if f.endswith('Sources'):
289                 deb_src_added = True
290                 source_line='deb-src '+fake_dirname+'/ /'
291             else:
292                 source_line='deb '+fake_dirname+'/ /'
293             listpath = self.cache_dir.preauthChild(self.apt_config['Dir::State']
294                                     ).preauthChild(self.apt_config['Dir::State::Lists']
295                                     ).child(apt_pkg.URItoFileName(fake_uri))
296             sources.write(source_line+'\n')
297             log.msg("Sources line: " + source_line)
298             sources_count = sources_count + 1
299
300             if listpath.exists():
301                 #we should empty the directory instead
302                 listpath.remove()
303             os.symlink(file.path, listpath.path)
304         sources.close()
305
306         if sources_count == 0:
307             log.msg("No Packages files available for %s backend"%(self.cache_dir.path))
308             return False
309
310         log.msg("Loading Packages database for "+self.cache_dir.path)
311         for key, value in self.apt_config.items():
312             apt_pkg.Config[key] = value
313
314         self.cache = apt_pkg.GetCache(OpProgress())
315         self.records = apt_pkg.GetPkgRecords(self.cache)
316         if deb_src_added:
317             self.srcrecords = apt_pkg.GetPkgSrcRecords()
318         else:
319             self.srcrecords = None
320
321         self.loaded = True
322         return True
323
324     def unload(self):
325         """Tries to make the packages server quit."""
326         if self.unload_later and self.unload_later.active():
327             self.unload_later.cancel()
328         self.unload_later = None
329         if self.loaded:
330             log.msg('Unloading the packages cache')
331             # This should save memory
332             del self.cache
333             del self.records
334             del self.srcrecords
335             del self.indexrecords
336             self.loaded = False
337
338     def cleanup(self):
339         """Cleanup and close any loaded caches."""
340         self.unload()
341         if self.unload_later and self.unload_later.active():
342             self.unload_later.cancel()
343         self.packages.close()
344         
345     def findHash(self, path):
346         """Find the hash for a given path in this mirror.
347         
348         @type path: C{string}
349         @param path: the path within the mirror of the file to lookup
350         @rtype: L{twisted.internet.defer.Deferred}
351         @return: a deferred so it can make sure the cache is loaded first
352         """
353         d = defer.Deferred()
354
355         deferLoad = self.load()
356         deferLoad.addCallback(self._findHash, path, d)
357         deferLoad.addErrback(self._findHash_error, path, d)
358         
359         return d
360
361     def _findHash_error(self, failure, path, d):
362         """An error occurred, return an empty hash."""
363         log.msg('An error occurred while looking up a hash for: %s' % path)
364         log.err(failure)
365         d.callback(HashObject())
366         return failure
367
368     def _findHash(self, loadResult, path, d):
369         """Search the records for the hash of a path.
370         
371         @type loadResult: C{boolean}
372         @param loadResult: whether apt's cache was successfully loaded
373         @type path: C{string}
374         @param path: the path within the mirror of the file to lookup
375         @type d: L{twisted.internet.defer.Deferred}
376         @param d: the deferred to callback with the result
377         """
378         if not loadResult:
379             d.callback(HashObject())
380             return loadResult
381         
382         h = HashObject()
383         
384         # First look for the path in the cache of index files
385         for release in self.indexrecords:
386             if path.startswith(release[:-7]):
387                 for indexFile in self.indexrecords[release]:
388                     if release[:-7] + indexFile == path:
389                         h.setFromIndexRecord(self.indexrecords[release][indexFile])
390                         d.callback(h)
391                         return loadResult
392         
393         package = path.split('/')[-1].split('_')[0]
394
395         # Check the binary packages
396         try:
397             for version in self.cache[package].VersionList:
398                 size = version.Size
399                 for verFile in version.FileList:
400                     if self.records.Lookup(verFile):
401                         if '/' + self.records.FileName == path:
402                             h.setFromPkgRecord(self.records, size)
403                             d.callback(h)
404                             return loadResult
405         except KeyError:
406             pass
407
408         # Check the source packages' files
409         if self.srcrecords:
410             self.srcrecords.Restart()
411             if self.srcrecords.Lookup(package):
412                 for f in self.srcrecords.Files:
413                     if path == '/' + f[2]:
414                         h.setFromSrcRecord(f)
415                         d.callback(h)
416                         return loadResult
417         
418         d.callback(h)
419         
420         # Have to pass the returned loadResult on in case other calls to this function are pending.
421         return loadResult
422
423 class TestAptPackages(unittest.TestCase):
424     """Unit tests for the AptPackages cache."""
425     
426     pending_calls = []
427     client = None
428     timeout = 10
429     packagesFile = ''
430     sourcesFile = ''
431     releaseFile = ''
432     
433     def setUp(self):
434         """Initializes the cache with files found in the traditional apt location."""
435         self.client = AptPackages(FilePath('/tmp/.apt-p2p'))
436     
437         # Find the largest index files that are for 'main'
438         self.packagesFile = os.popen('ls -Sr /var/lib/apt/lists/ | grep -E "_main_.*Packages$" | tail -n 1').read().rstrip('\n')
439         self.sourcesFile = os.popen('ls -Sr /var/lib/apt/lists/ | grep -E "_main_.*Sources$" | tail -n 1').read().rstrip('\n')
440         
441         # Find the Release file corresponding to the found Packages file
442         for f in os.walk('/var/lib/apt/lists').next()[2]:
443             if f[-7:] == "Release" and self.packagesFile.startswith(f[:-7]):
444                 self.releaseFile = f
445                 break
446
447         # Add all the found files to the PackageFileList
448         self.client.file_updated(self.releaseFile[self.releaseFile.find('_dists_'):].replace('_','/'), 
449                                  FilePath('/var/lib/apt/lists/' + self.releaseFile))
450         self.client.file_updated(self.packagesFile[self.packagesFile.find('_dists_'):].replace('_','/'), 
451                                  FilePath('/var/lib/apt/lists/' + self.packagesFile))
452         self.client.file_updated(self.sourcesFile[self.sourcesFile.find('_dists_'):].replace('_','/'), 
453                                  FilePath('/var/lib/apt/lists/' + self.sourcesFile))
454     
455     def test_pkg_hash(self):
456         """Tests loading the binary package records cache."""
457         self.client._load()
458
459         self.client.records.Lookup(self.client.cache['dpkg'].VersionList[0].FileList[0])
460         
461         pkg_hash = os.popen('grep -A 30 -E "^Package: dpkg$" ' + 
462                             '/var/lib/apt/lists/' + self.packagesFile + 
463                             ' | grep -E "^SHA1:" | head -n 1' + 
464                             ' | cut -d\  -f 2').read().rstrip('\n')
465
466         self.failUnless(self.client.records.SHA1Hash == pkg_hash, 
467                         "Hashes don't match: %s != %s" % (self.client.records.SHA1Hash, pkg_hash))
468
469     def test_src_hash(self):
470         """Tests loading the source package records cache."""
471         self.client._load()
472
473         self.client.srcrecords.Lookup('dpkg')
474
475         src_hashes = os.popen('grep -A 20 -E "^Package: dpkg$" ' + 
476                             '/var/lib/apt/lists/' + self.sourcesFile + 
477                             ' | grep -A 4 -E "^Files:" | grep -E "^ " ' + 
478                             ' | cut -d\  -f 2').read().split('\n')[:-1]
479
480         for f in self.client.srcrecords.Files:
481             self.failUnless(f[0] in src_hashes, "Couldn't find %s in: %r" % (f[0], src_hashes))
482
483     def test_index_hash(self):
484         """Tests loading the cache of index file information."""
485         self.client._load()
486
487         indexhash = self.client.indexrecords[self.releaseFile[self.releaseFile.find('_dists_'):].replace('_','/')]['main/binary-i386/Packages.bz2']['SHA1'][0]
488
489         idx_hash = os.popen('grep -A 3000 -E "^SHA1:" ' + 
490                             '/var/lib/apt/lists/' + self.releaseFile + 
491                             ' | grep -E " main/binary-i386/Packages.bz2$"'
492                             ' | head -n 1 | cut -d\  -f 2').read().rstrip('\n')
493
494         self.failUnless(indexhash == idx_hash, "Hashes don't match: %s != %s" % (indexhash, idx_hash))
495
496     def verifyHash(self, found_hash, path, true_hash):
497         self.failUnless(found_hash.hexexpected() == true_hash, 
498                     "%s hashes don't match: %s != %s" % (path, found_hash.hexexpected(), true_hash))
499
500     def test_findIndexHash(self):
501         """Tests finding the hash of a single index file."""
502         lastDefer = defer.Deferred()
503         
504         idx_hash = os.popen('grep -A 3000 -E "^SHA1:" ' + 
505                             '/var/lib/apt/lists/' + self.releaseFile + 
506                             ' | grep -E " main/binary-i386/Packages.bz2$"'
507                             ' | head -n 1 | cut -d\  -f 2').read().rstrip('\n')
508         idx_path = '/' + self.releaseFile[self.releaseFile.find('_dists_')+1:].replace('_','/')[:-7] + 'main/binary-i386/Packages.bz2'
509
510         d = self.client.findHash(idx_path)
511         d.addCallback(self.verifyHash, idx_path, idx_hash)
512
513         d.addBoth(lastDefer.callback)
514         return lastDefer
515
516     def test_findPkgHash(self):
517         """Tests finding the hash of a single binary package."""
518         lastDefer = defer.Deferred()
519         
520         pkg_hash = os.popen('grep -A 30 -E "^Package: dpkg$" ' + 
521                             '/var/lib/apt/lists/' + self.packagesFile + 
522                             ' | grep -E "^SHA1:" | head -n 1' + 
523                             ' | cut -d\  -f 2').read().rstrip('\n')
524         pkg_path = '/' + os.popen('grep -A 30 -E "^Package: dpkg$" ' + 
525                             '/var/lib/apt/lists/' + self.packagesFile + 
526                             ' | grep -E "^Filename:" | head -n 1' + 
527                             ' | cut -d\  -f 2').read().rstrip('\n')
528
529         d = self.client.findHash(pkg_path)
530         d.addCallback(self.verifyHash, pkg_path, pkg_hash)
531
532         d.addBoth(lastDefer.callback)
533         return lastDefer
534
535     def test_findSrcHash(self):
536         """Tests finding the hash of a single source package."""
537         lastDefer = defer.Deferred()
538         
539         src_dir = '/' + os.popen('grep -A 30 -E "^Package: dpkg$" ' + 
540                             '/var/lib/apt/lists/' + self.sourcesFile + 
541                             ' | grep -E "^Directory:" | head -n 1' + 
542                             ' | cut -d\  -f 2').read().rstrip('\n')
543         src_hashes = os.popen('grep -A 20 -E "^Package: dpkg$" ' + 
544                             '/var/lib/apt/lists/' + self.sourcesFile + 
545                             ' | grep -A 4 -E "^Files:" | grep -E "^ " ' + 
546                             ' | cut -d\  -f 2').read().split('\n')[:-1]
547         src_paths = os.popen('grep -A 20 -E "^Package: dpkg$" ' + 
548                             '/var/lib/apt/lists/' + self.sourcesFile + 
549                             ' | grep -A 4 -E "^Files:" | grep -E "^ " ' + 
550                             ' | cut -d\  -f 4').read().split('\n')[:-1]
551
552         i = choice(range(len(src_hashes)))
553         d = self.client.findHash(src_dir + '/' + src_paths[i])
554         d.addCallback(self.verifyHash, src_dir + '/' + src_paths[i], src_hashes[i])
555             
556         d.addBoth(lastDefer.callback)
557         return lastDefer
558
559     def test_multipleFindHash(self):
560         """Tests finding the hash of an index file, binary package, source package, and another index file."""
561         lastDefer = defer.Deferred()
562         
563         # Lookup a Packages.bz2 file
564         idx_hash = os.popen('grep -A 3000 -E "^SHA1:" ' + 
565                             '/var/lib/apt/lists/' + self.releaseFile + 
566                             ' | grep -E " main/binary-i386/Packages.bz2$"'
567                             ' | head -n 1 | cut -d\  -f 2').read().rstrip('\n')
568         idx_path = '/' + self.releaseFile[self.releaseFile.find('_dists_')+1:].replace('_','/')[:-7] + 'main/binary-i386/Packages.bz2'
569
570         d = self.client.findHash(idx_path)
571         d.addCallback(self.verifyHash, idx_path, idx_hash)
572
573         # Lookup the binary 'dpkg' package
574         pkg_hash = os.popen('grep -A 30 -E "^Package: dpkg$" ' + 
575                             '/var/lib/apt/lists/' + self.packagesFile + 
576                             ' | grep -E "^SHA1:" | head -n 1' + 
577                             ' | cut -d\  -f 2').read().rstrip('\n')
578         pkg_path = '/' + os.popen('grep -A 30 -E "^Package: dpkg$" ' + 
579                             '/var/lib/apt/lists/' + self.packagesFile + 
580                             ' | grep -E "^Filename:" | head -n 1' + 
581                             ' | cut -d\  -f 2').read().rstrip('\n')
582
583         d = self.client.findHash(pkg_path)
584         d.addCallback(self.verifyHash, pkg_path, pkg_hash)
585
586         # Lookup the source 'dpkg' package
587         src_dir = '/' + os.popen('grep -A 30 -E "^Package: dpkg$" ' + 
588                             '/var/lib/apt/lists/' + self.sourcesFile + 
589                             ' | grep -E "^Directory:" | head -n 1' + 
590                             ' | cut -d\  -f 2').read().rstrip('\n')
591         src_hashes = os.popen('grep -A 20 -E "^Package: dpkg$" ' + 
592                             '/var/lib/apt/lists/' + self.sourcesFile + 
593                             ' | grep -A 4 -E "^Files:" | grep -E "^ " ' + 
594                             ' | cut -d\  -f 2').read().split('\n')[:-1]
595         src_paths = os.popen('grep -A 20 -E "^Package: dpkg$" ' + 
596                             '/var/lib/apt/lists/' + self.sourcesFile + 
597                             ' | grep -A 4 -E "^Files:" | grep -E "^ " ' + 
598                             ' | cut -d\  -f 4').read().split('\n')[:-1]
599
600         for i in range(len(src_hashes)):
601             d = self.client.findHash(src_dir + '/' + src_paths[i])
602             d.addCallback(self.verifyHash, src_dir + '/' + src_paths[i], src_hashes[i])
603             
604         # Lookup a Sources.bz2 file
605         idx_hash = os.popen('grep -A 3000 -E "^SHA1:" ' + 
606                             '/var/lib/apt/lists/' + self.releaseFile + 
607                             ' | grep -E " main/source/Sources.bz2$"'
608                             ' | head -n 1 | cut -d\  -f 2').read().rstrip('\n')
609         idx_path = '/' + self.releaseFile[self.releaseFile.find('_dists_')+1:].replace('_','/')[:-7] + 'main/source/Sources.bz2'
610
611         d = self.client.findHash(idx_path)
612         d.addCallback(self.verifyHash, idx_path, idx_hash)
613
614         d.addBoth(lastDefer.callback)
615         return lastDefer
616
617     def tearDown(self):
618         for p in self.pending_calls:
619             if p.active():
620                 p.cancel()
621         self.pending_calls = []
622         self.client.cleanup()
623         self.client = None