]> 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                 self.reject("%s: 'ar t' invocation failed." % (self.filename))
103                 self.reject(utils.prefix_multi_line_string(output, " [ar output:] "))
104             self.chunks = output.split('\n')
105
106
107
108     def __unpack(self):
109         # Internal function which extracts the contents of the .ar to
110         # a temporary directory
111
112         if not self.tmpdir:
113             tmpdir = utils.temp_dirname()
114             cwd = os.getcwd()
115             try:
116                 os.chdir( tmpdir )
117                 cmd = "ar x %s %s %s" % (os.path.join(cwd,self.filename), self.chunks[1], self.chunks[2])
118                 (result, output) = commands.getstatusoutput(cmd)
119                 if result != 0:
120                     self.reject("%s: '%s' invocation failed." % (self.filename, cmd))
121                     self.reject(utils.prefix_multi_line_string(output, " [ar output:] "))
122                 else:
123                     self.tmpdir = tmpdir
124                     atexit.register( self._cleanup )
125
126             finally:
127                 os.chdir( cwd )
128
129     def valid_deb(self):
130         """
131         Check deb contents making sure the .deb contains:
132           1. debian-binary
133           2. control.tar.gz
134           3. data.tar.gz or data.tar.bz2
135         in that order, and nothing else.
136         """
137         self.__scan_ar()
138         rejected = not self.chunks
139         if len(self.chunks) != 3:
140             rejected = True
141             self.reject("%s: found %d chunks, expected 3." % (self.filename, len(self.chunks)))
142         if self.chunks[0] != "debian-binary":
143             rejected = True
144             self.reject("%s: first chunk is '%s', expected 'debian-binary'." % (self.filename, self.chunks[0]))
145         if not rejected and self.chunks[1] != "control.tar.gz":
146             rejected = True
147             self.reject("%s: second chunk is '%s', expected 'control.tar.gz'." % (self.filename, self.chunks[1]))
148         if not rejected and self.chunks[2] not in [ "data.tar.bz2", "data.tar.gz" ]:
149             rejected = True
150             self.reject("%s: third chunk is '%s', expected 'data.tar.gz' or 'data.tar.bz2'." % (self.filename, self.chunks[2]))
151
152         return not rejected
153
154     def scan_package(self, bootstrap_id=0):
155         """
156         Unpack the .deb, do sanity checking, and gather info from it.
157
158         Currently information gathering consists of getting the contents list. In
159         the hopefully near future, it should also include gathering info from the
160         control file.
161
162         @ptype bootstrap_id: int
163         @param bootstrap_id: the id of the binary these packages
164           should be associated or zero meaning we are not bootstrapping
165           so insert into a temporary table
166
167         @return True if the deb is valid and contents were imported
168         """
169         rejected = not self.valid_deb()
170         if not rejected:
171             self.__unpack()
172
173             result = False
174
175             cwd = os.getcwd()
176             if not rejected and self.tmpdir:
177                 try:
178                     os.chdir(self.tmpdir)
179                     if self.chunks[1] == "control.tar.gz":
180                         control = tarfile.open(os.path.join(self.tmpdir, "control.tar.gz" ), "r:gz")
181                         control.extract('./control', self.tmpdir )
182                     if self.chunks[2] == "data.tar.gz":
183                         data = tarfile.open(os.path.join(self.tmpdir, "data.tar.gz"), "r:gz")
184                     elif self.chunks[2] == "data.tar.bz2":
185                         data = tarfile.open(os.path.join(self.tmpdir, "data.tar.bz2" ), "r:bz2")
186
187                     if bootstrap_id:
188                         result = DBConn().insert_content_paths(bootstrap_id, [tarinfo.name for tarinfo in data if not tarinfo.isdir()])
189                     else:
190                         pkgs = deb822.Packages.iter_paragraphs(file(os.path.join(self.tmpdir,'control')))
191                         pkg = pkgs.next()
192                         result = DBConn().insert_pending_content_paths(pkg, [tarinfo.name for tarinfo in data if not tarinfo.isdir()])
193
194                 except:
195                     traceback.print_exc()
196
197             os.chdir(cwd)
198         return result
199
200     def check_utf8_package(self, package):
201         """
202         Unpack the .deb, do sanity checking, and gather info from it.
203
204         Currently information gathering consists of getting the contents list. In
205         the hopefully near future, it should also include gathering info from the
206         control file.
207
208         @ptype bootstrap_id: int
209         @param bootstrap_id: the id of the binary these packages
210           should be associated or zero meaning we are not bootstrapping
211           so insert into a temporary table
212
213         @return True if the deb is valid and contents were imported
214         """
215         rejected = not self.valid_deb()
216         self.__unpack()
217
218         if not rejected and self.tmpdir:
219             cwd = os.getcwd()
220             try:
221                 os.chdir(self.tmpdir)
222                 if self.chunks[1] == "control.tar.gz":
223                     control = tarfile.open(os.path.join(self.tmpdir, "control.tar.gz" ), "r:gz")
224                     control.extract('control', self.tmpdir )
225                 if self.chunks[2] == "data.tar.gz":
226                     data = tarfile.open(os.path.join(self.tmpdir, "data.tar.gz"), "r:gz")
227                 elif self.chunks[2] == "data.tar.bz2":
228                     data = tarfile.open(os.path.join(self.tmpdir, "data.tar.bz2" ), "r:bz2")
229
230                 for tarinfo in data:
231                     try:
232                         unicode( tarinfo.name )
233                     except:
234                         print >> sys.stderr, "E: %s has non-unicode filename: %s" % (package,tarinfo.name)
235
236             except:
237                 traceback.print_exc()
238                 result = False
239
240             os.chdir(cwd)
241
242 if __name__ == "__main__":
243     Binary( "/srv/ftp.debian.org/queue/accepted/halevt_0.1.3-2_amd64.deb" ).scan_package()
244