]> git.decadent.org.uk Git - dak.git/blob - tea
lots and lots of python 2.1 changes. rene: remove bogus argument handling. katie...
[dak.git] / tea
1 #!/usr/bin/env python
2
3 # Sanity check the database
4 # Copyright (C) 2000, 2001, 2002  James Troup <james@nocrew.org>
5 # $Id: tea,v 1.21 2002-10-16 02:47:32 troup Exp $
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 #   And, lo, a great and menacing voice rose from the depths, and with
22 #   great wrath and vehemence it's voice boomed across the
23 #   land... ``hehehehehehe... that *tickles*''
24 #                                                       -- aj on IRC
25
26 ################################################################################
27
28 import pg, sys, os, stat, time
29 import utils, db_access
30 import apt_pkg, apt_inst;
31
32 ################################################################################
33
34 Cnf = None;
35 projectB = None;
36 db_files = {};
37 waste = 0.0;
38 excluded = {};
39 current_file = None;
40 future_files = {};
41 current_time = time.time();
42
43 ################################################################################
44
45 def process_dir (unused, dirname, filenames):
46     global waste, db_files, excluded;
47
48     if dirname.find('/disks-') != -1 or dirname.find('upgrade-') != -1:
49         return;
50     # hack; can't handle .changes files
51     if dirname.find('proposed-updates') != -1:
52         return;
53     for name in filenames:
54         filename = os.path.abspath(dirname+'/'+name);
55         filename = filename.replace('potato-proposed-updates', 'proposed-updates');
56         if os.path.isfile(filename) and not os.path.islink(filename) and not db_files.has_key(filename) and not excluded.has_key(filename):
57             waste += os.stat(filename)[stat.ST_SIZE];
58             print filename
59
60 ################################################################################
61
62 def check_files():
63     global db_files;
64
65     print "Building list of database files...";
66     q = projectB.query("SELECT l.path, f.filename FROM files f, location l WHERE f.location = l.id")
67     ql = q.getresult();
68
69     db_files.clear();
70     for i in ql:
71         filename = os.path.abspath(i[0] + i[1]);
72         db_files[filename] = "";
73         if os.access(filename, os.R_OK) == 0:
74             utils.warn("'%s' doesn't exist." % (filename));
75
76     file = utils.open_file(Cnf["Dir::Override"]+'override.unreferenced');
77     for filename in file.readlines():
78         filename = filename[:-1];
79         excluded[filename] = "";
80
81     print "Checking against existent files...";
82
83     os.path.walk(Cnf["Dir::Root"]+'dists/', process_dir, None);
84
85     print
86     print "%s wasted..." % (utils.size_type(waste));
87
88 ################################################################################
89
90 def check_dscs():
91     count = 0;
92     suite = 'unstable';
93     for component in Cnf.SubTree("Component").List():
94         if component == "mixed":
95             continue;
96         component = component.lower();
97         list_filename = '%s%s_%s_source.list' % (Cnf["Dir::Lists"], suite, component);
98         list_file = utils.open_file(list_filename);
99         for line in list_file.readlines():
100             file = line[:-1];
101             try:
102                 utils.parse_changes(file, dsc_whitespace_rules=1);
103             except utils.invalid_dsc_format_exc, line:
104                 utils.warn("syntax error in .dsc file '%s', line %s." % (file, line));
105                 count += 1;
106
107     if count:
108         utils.warn("Found %s invalid .dsc files." % (count));
109
110 ################################################################################
111
112 def check_override():
113     for suite in [ "stable", "unstable" ]:
114         print suite
115         print "-------------"
116         print
117         suite_id = db_access.get_suite_id(suite);
118         q = projectB.query("""
119 SELECT DISTINCT b.package FROM binaries b, bin_associations ba
120  WHERE b.id = ba.bin AND ba.suite = %s AND NOT EXISTS
121        (SELECT * FROM override o WHERE o.suite = %s AND o.package = b.package)"""
122                            % (suite_id, suite_id));
123         print q
124         q = projectB.query("""
125 SELECT DISTINCT s.source FROM source s, src_associations sa
126   WHERE s.id = sa.source AND sa.suite = %s AND NOT EXISTS
127        (SELECT * FROM override o WHERE o.suite = %s and o.package = s.source)"""
128                            % (suite_id, suite_id));
129         print q
130
131 ################################################################################
132
133 # Ensure that the source files for any given package is all in one
134 # directory so that 'apt-get source' works...
135
136 def check_source_in_one_dir():
137     # Not the most enterprising method, but hey...
138     broken_count = 0;
139     q = projectB.query("SELECT id FROM source;");
140     for i in q.getresult():
141         source_id = i[0];
142         q2 = projectB.query("""
143 SELECT l.path, f.filename FROM files f, dsc_files df, location l WHERE df.source = %s AND f.id = df.file AND l.id = f.location"""
144                             % (source_id));
145         first_path = "";
146         first_filename = "";
147         broken = 0;
148         for j in q2.getresult():
149             filename = j[0] + j[1];
150             path = os.path.dirname(filename);
151             if first_path == "":
152                 first_path = path;
153                 first_filename = filename;
154             elif first_path != path:
155                 symlink = path + '/' + os.path.basename(first_filename);
156                 if not os.path.exists(symlink):
157                     broken = 1;
158                     print "WOAH, we got a live one here... %s [%s] {%s}" % (filename, source_id, symlink);
159         if broken:
160             broken_count += 1;
161     print "Found %d source packages where the source is not all in one directory." % (broken_count);
162
163 ################################################################################
164
165 def check_md5sums():
166     print "Getting file information from database...";
167     q = projectB.query("SELECT l.path, f.filename, f.md5sum, f.size FROM files f, location l WHERE f.location = l.id")
168     ql = q.getresult();
169
170     print "Checking file md5sums & sizes...";
171     for i in ql:
172         filename = os.path.abspath(i[0] + i[1]);
173         db_md5sum = i[2];
174         db_size = int(i[3]);
175         try:
176             file = utils.open_file(filename);
177         except:
178             utils.warn("can't open '%s'." % (filename));
179             continue;
180         md5sum = apt_pkg.md5sum(file);
181         size = os.stat(filename)[stat.ST_SIZE];
182         if md5sum != db_md5sum:
183             utils.warn("**WARNING** md5sum mismatch for '%s' ('%s' [current] vs. '%s' [db])." % (filename, md5sum, db_md5sum));
184         if size != db_size:
185             utils.warn("**WARNING** size mismatch for '%s' ('%s' [current] vs. '%s' [db])." % (filename, size, db_size));
186
187     print "Done."
188
189 ################################################################################
190 #
191 # Check all files for timestamps in the future; common from hardware
192 # (e.g. alpha) which have far-future dates as their default dates.
193
194
195
196 def Ent(Kind,Name,Link,Mode,UID,GID,Size,MTime,Major,Minor):
197     global future_files;
198
199     if MTime > current_time:
200         future_files[current_file] = MTime;
201         print "%s: %s '%s','%s',%u,%u,%u,%u,%u,%u,%u" % (current_file, Kind,Name,Link,Mode,UID,GID,Size, MTime, Major, Minor);
202
203 def check_timestamps():
204     global current_file;
205
206     q = projectB.query("SELECT l.path, f.filename FROM files f, location l WHERE f.location = l.id AND f.filename ~ '.deb$'")
207     ql = q.getresult();
208     db_files.clear();
209     count = 0;
210     for i in ql:
211         filename = os.path.abspath(i[0] + i[1]);
212         if os.access(filename, os.R_OK):
213             file = utils.open_file(filename);
214             current_file = filename;
215             sys.stderr.write("Processing %s.\n" % (filename));
216             apt_inst.debExtract(file,Ent,"control.tar.gz");
217             file.seek(0);
218             apt_inst.debExtract(file,Ent,"data.tar.gz");
219             count += 1;
220     print "Checked %d files (out of %d)." % (count, len(db_files.keys()));
221
222 ################################################################################
223
224 def check_missing_tar_gz_in_dsc():
225     count = 0;
226
227     print "Building list of database files...";
228     q = projectB.query("SELECT l.path, f.filename FROM files f, location l WHERE f.location = l.id AND f.filename ~ '.dsc$'");
229     ql = q.getresult();
230     if ql:
231         print "Checking %d files..." % len(ql);
232     else:
233         print "No files to check."
234     for i in ql:
235         filename = os.path.abspath(i[0] + i[1]);
236         try:
237             # NB: don't enforce .dsc syntax
238             dsc = utils.parse_changes(filename);
239         except:
240             utils.fubar("error parsing .dsc file '%s'." % (filename));
241         dsc_files = utils.build_file_list(dsc, is_a_dsc=1);
242         has_tar = 0;
243         for file in dsc_files.keys():
244             m = utils.re_issource.match(file);
245             if not m:
246                 utils.fubar("%s not recognised as source." % (file));
247             type = m.group(3);
248             if type == "orig.tar.gz" or type == "tar.gz":
249                 has_tar = 1;
250         if not has_tar:
251             utils.warn("%s has no .tar.gz in the .dsc file." % (file));
252             count += 1;
253
254     if count:
255         utils.warn("Found %s invalid .dsc files." % (count));
256
257 ################################################################################
258
259 def main ():
260     global Cnf, projectB, db_files, waste, excluded;
261
262     Cnf = utils.get_conf()
263
264     apt_pkg.ParseCommandLine(Cnf,[],sys.argv);
265     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]));
266     db_access.init(Cnf, projectB);
267
268     #check_md5sums();
269     #check_source_in_one_dir();
270     #check_override();
271     #check_dscs();
272     #check_files();
273     #check_timestamps();
274     check_missing_tar_gz_in_dsc();
275
276 #######################################################################################
277
278 if __name__ == '__main__':
279     main();
280