]> git.decadent.org.uk Git - dak.git/blob - daklib/binary.py
Merge commit 'stew/content_generation' into merge
[dak.git] / daklib / binary.py
1 #!/usr/bin/python
2
3 """
4 Functions related debian binary packages
5
6 @contact: Debian FTPMaster <ftpmaster@debian.org>
7 @copyright: 2009  Mike O'Connor <stew@debian.org>
8 @license: GNU General Public License version 2 or later
9 """
10
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
15
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
25 ################################################################################
26
27 # <Ganneff> are we going the xorg way?
28 # <Ganneff> a dak without a dak.conf?
29 # <stew> automatically detect the wrong settings at runtime?
30 # <Ganneff> yes!
31 # <mhy> well, we'll probably always need dak.conf (how do you get the database setting
32 # <mhy> but removing most of the config into the database seems sane
33 # <Ganneff> mhy: dont spoil the fun
34 # <Ganneff> mhy: and i know how. we nmap localhost and check all open ports
35 # <Ganneff> maybe one answers to sql
36 # <stew> we will discover projectb via avahi
37 # <mhy> you're both sick
38 # <mhy> really fucking sick
39
40 ################################################################################
41
42 import os
43 import sys
44 import shutil
45 import tempfile
46 import tarfile
47 import commands
48 import traceback
49 import atexit
50 from debian_bundle import deb822
51 from dbconn import DBConn
52 from config import Config
53 import logging
54 import utils
55
56 class Binary(object):
57     def __init__(self, filename, reject=None):
58         """
59         @ptype filename: string
60         @param filename: path of a .deb
61
62         @ptype reject: function
63         @param reject: a function to log reject messages to
64         """
65         self.filename = filename
66         self.tmpdir = None
67         self.chunks = None
68         self.wrapped_reject = reject
69
70     def reject(self, message):
71         """
72         if we were given a reject function, send the reject message,
73         otherwise send it to stderr.
74         """
75         if self.wrapped_reject:
76             self.wrapped_reject(message)
77         else:
78             print >> sys.stderr, message
79
80     def __del__(self):
81         """
82         make sure we cleanup when we are garbage collected.
83         """
84         self._cleanup()
85
86     def _cleanup(self):
87         """
88         we need to remove the temporary directory, if we created one
89         """
90         if self.tmpdir and os.path.exists(self.tmpdir):
91             shutil.rmtree(self.tmpdir)
92             self.tmpdir = None
93
94     def __scan_ar(self):
95         # get a list of the ar contents
96         if not self.chunks:
97
98             cmd = "ar t %s" % (self.filename)
99             (result, output) = commands.getstatusoutput(cmd)
100             if result != 0:
101                 rejected = True
102                 print("%s: 'ar t' invocation failed." % (self.filename))
103                 self.reject("%s: 'ar t' invocation failed." % (self.filename))
104                 self.reject(utils.prefix_multi_line_string(output, " [ar output:] "))
105             self.chunks = output.split('\n')
106
107
108
109     def __unpack(self):
110         # Internal function which extracts the contents of the .ar to
111         # a temporary directory
112
113         if not self.tmpdir:
114             tmpdir = utils.temp_dirname()
115             cwd = os.getcwd()
116             try:
117                 os.chdir( tmpdir )
118                 cmd = "ar x %s %s %s" % (os.path.join(cwd,self.filename), self.chunks[1], self.chunks[2])
119                 (result, output) = commands.getstatusoutput(cmd)
120                 if result != 0:
121                     print("%s: '%s' invocation failed." % (self.filename, cmd))
122                     self.reject("%s: '%s' invocation failed." % (self.filename, cmd))
123                     self.reject(utils.prefix_multi_line_string(output, " [ar output:] "))
124                 else:
125                     self.tmpdir = tmpdir
126                     atexit.register( self._cleanup )
127
128             finally:
129                 os.chdir( cwd )
130
131     def valid_deb(self):
132         """
133         Check deb contents making sure the .deb contains:
134           1. debian-binary
135           2. control.tar.gz
136           3. data.tar.gz or data.tar.bz2
137         in that order, and nothing else.
138         """
139         self.__scan_ar()
140         rejected = not self.chunks
141         if len(self.chunks) != 3:
142             rejected = True
143             self.reject("%s: found %d chunks, expected 3." % (self.filename, len(self.chunks)))
144         if self.chunks[0] != "debian-binary":
145             rejected = True
146             self.reject("%s: first chunk is '%s', expected 'debian-binary'." % (self.filename, self.chunks[0]))
147         if not rejected and self.chunks[1] != "control.tar.gz":
148             rejected = True
149             self.reject("%s: second chunk is '%s', expected 'control.tar.gz'." % (self.filename, self.chunks[1]))
150         if not rejected and self.chunks[2] not in [ "data.tar.bz2", "data.tar.gz" ]:
151             rejected = True
152             self.reject("%s: third chunk is '%s', expected 'data.tar.gz' or 'data.tar.bz2'." % (self.filename, self.chunks[2]))
153
154         return not rejected
155
156     def scan_package(self, bootstrap_id=0):
157         """
158         Unpack the .deb, do sanity checking, and gather info from it.
159
160         Currently information gathering consists of getting the contents list. In
161         the hopefully near future, it should also include gathering info from the
162         control file.
163
164         @ptype bootstrap_id: int
165         @param bootstrap_id: the id of the binary these packages
166           should be associated or zero meaning we are not bootstrapping
167           so insert into a temporary table
168
169         @return True if the deb is valid and contents were imported
170         """
171         result = False
172         rejected = not self.valid_deb()
173         if not rejected:
174             self.__unpack()
175
176
177             cwd = os.getcwd()
178             if not rejected and self.tmpdir:
179                 try:
180                     os.chdir(self.tmpdir)
181                     if self.chunks[1] == "control.tar.gz":
182                         control = tarfile.open(os.path.join(self.tmpdir, "control.tar.gz" ), "r:gz")
183                         control.extract('./control', self.tmpdir )
184                     if self.chunks[2] == "data.tar.gz":
185                         data = tarfile.open(os.path.join(self.tmpdir, "data.tar.gz"), "r:gz")
186                     elif self.chunks[2] == "data.tar.bz2":
187                         data = tarfile.open(os.path.join(self.tmpdir, "data.tar.bz2" ), "r:bz2")
188
189                     if bootstrap_id:
190                         result = DBConn().insert_content_paths(bootstrap_id, [tarinfo.name for tarinfo in data if not tarinfo.isdir()])
191                     else:
192                         pkgs = deb822.Packages.iter_paragraphs(file(os.path.join(self.tmpdir,'control')))
193                         pkg = pkgs.next()
194                         result = DBConn().insert_pending_content_paths(pkg, [tarinfo.name for tarinfo in data if not tarinfo.isdir()])
195
196                 except:
197                     traceback.print_exc()
198
199             os.chdir(cwd)
200         return result
201
202     def check_utf8_package(self, package):
203         """
204         Unpack the .deb, do sanity checking, and gather info from it.
205
206         Currently information gathering consists of getting the contents list. In
207         the hopefully near future, it should also include gathering info from the
208         control file.
209
210         @ptype bootstrap_id: int
211         @param bootstrap_id: the id of the binary these packages
212           should be associated or zero meaning we are not bootstrapping
213           so insert into a temporary table
214
215         @return True if the deb is valid and contents were imported
216         """
217         rejected = not self.valid_deb()
218         self.__unpack()
219
220         if not rejected and self.tmpdir:
221             cwd = os.getcwd()
222             try:
223                 os.chdir(self.tmpdir)
224                 if self.chunks[1] == "control.tar.gz":
225                     control = tarfile.open(os.path.join(self.tmpdir, "control.tar.gz" ), "r:gz")
226                     control.extract('control', self.tmpdir )
227                 if self.chunks[2] == "data.tar.gz":
228                     data = tarfile.open(os.path.join(self.tmpdir, "data.tar.gz"), "r:gz")
229                 elif self.chunks[2] == "data.tar.bz2":
230                     data = tarfile.open(os.path.join(self.tmpdir, "data.tar.bz2" ), "r:bz2")
231
232                 for tarinfo in data:
233                     try:
234                         unicode( tarinfo.name )
235                     except:
236                         print >> sys.stderr, "E: %s has non-unicode filename: %s" % (package,tarinfo.name)
237
238             except:
239                 traceback.print_exc()
240                 result = False
241
242             os.chdir(cwd)
243
244 if __name__ == "__main__":
245     Binary( "/srv/ftp.debian.org/queue/accepted/halevt_0.1.3-2_amd64.deb" ).scan_package()
246