]> git.decadent.org.uk Git - dak.git/blob - utils.py
2004-11-27 James Troup <james@nocrew.org> * utils.py (re_no_epoch): s/\*/+/ as...
[dak.git] / utils.py
1 #!/usr/bin/env python
2
3 # Utility functions
4 # Copyright (C) 2000, 2001, 2002, 2003, 2004  James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.72 2004-11-27 18:12:57 troup Exp $
6
7 ################################################################################
8
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
23 ################################################################################
24
25 import commands, encodings.ascii, encodings.utf_8, encodings.latin_1, \
26        email.Header, os, pwd, re, select, socket, shutil, string, sys, \
27        tempfile, traceback;
28 import apt_pkg;
29 import db_access;
30
31 ################################################################################
32
33 re_comments = re.compile(r"\#.*")
34 re_no_epoch = re.compile(r"^\d+\:")
35 re_no_revision = re.compile(r"-[^-]+$")
36 re_arch_from_filename = re.compile(r"/binary-[^/]+/")
37 re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
38 re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$");
39 re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$");
40
41 re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)");
42 re_multi_line_field = re.compile(r"^\s(.*)");
43 re_taint_free = re.compile(r"^[-+~/\.\w]+$");
44
45 re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>");
46
47 changes_parse_error_exc = "Can't parse line in .changes file";
48 invalid_dsc_format_exc = "Invalid .dsc file";
49 nk_format_exc = "Unknown Format: in .changes file";
50 no_files_exc = "No Files: field in .dsc or .changes file.";
51 cant_open_exc = "Can't open file";
52 unknown_hostname_exc = "Unknown hostname";
53 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
54 file_exists_exc = "Destination file exists";
55 sendmail_failed_exc = "Sendmail invocation failed";
56 tried_too_hard_exc = "Tried too hard to find a free filename.";
57
58 default_config = "/etc/katie/katie.conf";
59 default_apt_config = "/etc/katie/apt.conf";
60
61 ################################################################################
62
63 class Error(Exception):
64     """Base class for exceptions in this module."""
65     pass;
66
67 class ParseMaintError(Error):
68     """Exception raised for errors in parsing a maintainer field.
69
70     Attributes:
71        message -- explanation of the error
72     """
73
74     def __init__(self, message):
75         self.args = message,;
76         self.message = message;
77
78 ################################################################################
79
80 def open_file(filename, mode='r'):
81     try:
82         f = open(filename, mode);
83     except IOError:
84         raise cant_open_exc, filename;
85     return f
86
87 ################################################################################
88
89 def our_raw_input(prompt=""):
90     if prompt:
91         sys.stdout.write(prompt);
92     sys.stdout.flush();
93     try:
94         ret = raw_input();
95         return ret;
96     except EOFError:
97         sys.stderr.write("\nUser interrupt (^D).\n");
98         raise SystemExit;
99
100 ################################################################################
101
102 def str_isnum (s):
103     for c in s:
104         if c not in string.digits:
105             return 0;
106     return 1;
107
108 ################################################################################
109
110 def extract_component_from_section(section):
111     component = "";
112
113     if section.find('/') != -1:
114         component = section.split('/')[0];
115     if component.lower() == "non-us" and section.find('/') != -1:
116         s = component + '/' + section.split('/')[1];
117         if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
118             component = s;
119
120     if section.lower() == "non-us":
121         component = "non-US/main";
122
123     # non-US prefix is case insensitive
124     if component.lower()[:6] == "non-us":
125         component = "non-US"+component[6:];
126
127     # Expand default component
128     if component == "":
129         if Cnf.has_key("Component::%s" % section):
130             component = section;
131         else:
132             component = "main";
133     elif component == "non-US":
134         component = "non-US/main";
135
136     return (section, component);
137
138 ################################################################################
139
140 def parse_changes(filename, signing_rules=0):
141     """Parses a changes file and returns a dictionary where each field is a
142 key.  The mandatory first argument is the filename of the .changes
143 file.
144
145 signing_rules is an optional argument:
146
147  o If signing_rules == -1, no signature is required.
148  o If signing_rules == 0 (the default), a signature is required.
149  o If signing_rules == 1, it turns on the same strict format checking
150    as dpkg-source.
151
152 The rules for (signing_rules == 1)-mode are:
153
154   o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
155     followed by any PGP header data and must end with a blank line.
156
157   o The data section must end with a blank line and must be followed by
158     "-----BEGIN PGP SIGNATURE-----".
159 """
160
161     error = "";
162     changes = {};
163
164     changes_in = open_file(filename);
165     lines = changes_in.readlines();
166
167     if not lines:
168         raise changes_parse_error_exc, "[Empty changes file]";
169
170     # Reindex by line number so we can easily verify the format of
171     # .dsc files...
172     index = 0;
173     indexed_lines = {};
174     for line in lines:
175         index += 1;
176         indexed_lines[index] = line[:-1];
177
178     inside_signature = 0;
179
180     num_of_lines = len(indexed_lines.keys());
181     index = 0;
182     first = -1;
183     while index < num_of_lines:
184         index += 1;
185         line = indexed_lines[index];
186         if line == "":
187             if signing_rules == 1:
188                 index += 1;
189                 if index > num_of_lines:
190                     raise invalid_dsc_format_exc, index;
191                 line = indexed_lines[index];
192                 if not line.startswith("-----BEGIN PGP SIGNATURE"):
193                     raise invalid_dsc_format_exc, index;
194                 inside_signature = 0;
195                 break;
196             else:
197                 continue;
198         if line.startswith("-----BEGIN PGP SIGNATURE"):
199             break;
200         if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
201             inside_signature = 1;
202             if signing_rules == 1:
203                 while index < num_of_lines and line != "":
204                     index += 1;
205                     line = indexed_lines[index];
206             continue;
207         # If we're not inside the signed data, don't process anything
208         if signing_rules >= 0 and not inside_signature:
209             continue;
210         slf = re_single_line_field.match(line);
211         if slf:
212             field = slf.groups()[0].lower();
213             changes[field] = slf.groups()[1];
214             first = 1;
215             continue;
216         if line == " .":
217             changes[field] += '\n';
218             continue;
219         mlf = re_multi_line_field.match(line);
220         if mlf:
221             if first == -1:
222                 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line);
223             if first == 1 and changes[field] != "":
224                 changes[field] += '\n';
225             first = 0;
226             changes[field] += mlf.groups()[0] + '\n';
227             continue;
228         error += line;
229
230     if signing_rules == 1 and inside_signature:
231         raise invalid_dsc_format_exc, index;
232
233     changes_in.close();
234     changes["filecontents"] = "".join(lines);
235
236     if error:
237         raise changes_parse_error_exc, error;
238
239     return changes;
240
241 ################################################################################
242
243 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
244
245 def build_file_list(changes, is_a_dsc=0):
246     files = {};
247
248     # Make sure we have a Files: field to parse...
249     if not changes.has_key("files"):
250         raise no_files_exc;
251
252     # Make sure we recognise the format of the Files: field
253     format = changes.get("format", "");
254     if format != "":
255         format = float(format);
256     if not is_a_dsc and (format < 1.5 or format > 2.0):
257         raise nk_format_exc, format;
258
259     # Parse each entry/line:
260     for i in changes["files"].split('\n'):
261         if not i:
262             break;
263         s = i.split();
264         section = priority = "";
265         try:
266             if is_a_dsc:
267                 (md5, size, name) = s;
268             else:
269                 (md5, size, section, priority, name) = s;
270         except ValueError:
271             raise changes_parse_error_exc, i;
272
273         if section == "":
274             section = "-";
275         if priority == "":
276             priority = "-";
277
278         (section, component) = extract_component_from_section(section);
279
280         files[name] = Dict(md5sum=md5, size=size, section=section,
281                            priority=priority, component=component);
282
283     return files
284
285 ################################################################################
286
287 def force_to_utf8(s):
288     """Forces a string to UTF-8.  If the string isn't already UTF-8,
289 it's assumed to be ISO-8859-1."""
290     try:
291         unicode(s, 'utf-8');
292         return s;
293     except UnicodeError:
294         latin1_s = unicode(s,'iso8859-1');
295         return latin1_s.encode('utf-8');
296
297 def rfc2047_encode(s):
298     """Encodes a (header) string per RFC2047 if necessary.  If the
299 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
300     try:
301         encodings.ascii.Codec().decode(s);
302         return s;
303     except UnicodeError:
304         pass;
305     try:
306         encodings.utf_8.Codec().decode(s);
307         h = email.Header.Header(s, 'utf-8', 998);
308         return str(h);
309     except UnicodeError:
310         h = email.Header.Header(s, 'iso-8859-1', 998);
311         return str(h);
312
313 ################################################################################
314
315 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
316 #          with it. I know - I'll fix the suckage and make things
317 #          incompatible!'
318
319 def fix_maintainer (maintainer):
320     """Parses a Maintainer or Changed-By field and returns:
321   (1) an RFC822 compatible version,
322   (2) an RFC2047 compatible version,
323   (3) the name
324   (4) the email
325
326 The name is forced to UTF-8 for both (1) and (3).  If the name field
327 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
328 switched to 'email (name)' format."""
329     maintainer = maintainer.strip()
330     if not maintainer:
331         return ('', '', '', '');
332
333     if maintainer.find("<") == -1:
334         email = maintainer;
335         name = "";
336     elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
337         email = maintainer[1:-1];
338         name = "";
339     else:
340         m = re_parse_maintainer.match(maintainer);
341         if not m:
342             raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
343         name = m.group(1);
344         email = m.group(2);
345
346     # Get an RFC2047 compliant version of the name
347     rfc2047_name = rfc2047_encode(name);
348
349     # Force the name to be UTF-8
350     name = force_to_utf8(name);
351
352     if name.find(',') != -1 or name.find('.') != -1:
353         rfc822_maint = "%s (%s)" % (email, name);
354         rfc2047_maint = "%s (%s)" % (email, rfc2047_name);
355     else:
356         rfc822_maint = "%s <%s>" % (name, email);
357         rfc2047_maint = "%s <%s>" % (rfc2047_name, email);
358
359     if email.find("@") == -1 and email.find("buildd_") != 0:
360         raise ParseMaintError, "No @ found in email address part."
361
362     return (rfc822_maint, rfc2047_maint, name, email);
363
364 ################################################################################
365
366 # sendmail wrapper, takes _either_ a message string or a file as arguments
367 def send_mail (message, filename=""):
368         # If we've been passed a string dump it into a temporary file
369         if message:
370             filename = tempfile.mktemp();
371             fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
372             os.write (fd, message);
373             os.close (fd);
374
375         # Invoke sendmail
376         (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
377         if (result != 0):
378             raise sendmail_failed_exc, output;
379
380         # Clean up any temporary files
381         if message:
382             os.unlink (filename);
383
384 ################################################################################
385
386 def poolify (source, component):
387     if component:
388         component += '/';
389     # FIXME: this is nasty
390     component = component.lower().replace("non-us/", "non-US/");
391     if source[:3] == "lib":
392         return component + source[:4] + '/' + source + '/'
393     else:
394         return component + source[:1] + '/' + source + '/'
395
396 ################################################################################
397
398 def move (src, dest, overwrite = 0, perms = 0664):
399     if os.path.exists(dest) and os.path.isdir(dest):
400         dest_dir = dest;
401     else:
402         dest_dir = os.path.dirname(dest);
403     if not os.path.exists(dest_dir):
404         umask = os.umask(00000);
405         os.makedirs(dest_dir, 02775);
406         os.umask(umask);
407     #print "Moving %s to %s..." % (src, dest);
408     if os.path.exists(dest) and os.path.isdir(dest):
409         dest += '/' + os.path.basename(src);
410     # Don't overwrite unless forced to
411     if os.path.exists(dest):
412         if not overwrite:
413             fubar("Can't move %s to %s - file already exists." % (src, dest));
414         else:
415             if not os.access(dest, os.W_OK):
416                 fubar("Can't move %s to %s - can't write to existing file." % (src, dest));
417     shutil.copy2(src, dest);
418     os.chmod(dest, perms);
419     os.unlink(src);
420
421 def copy (src, dest, overwrite = 0, perms = 0664):
422     if os.path.exists(dest) and os.path.isdir(dest):
423         dest_dir = dest;
424     else:
425         dest_dir = os.path.dirname(dest);
426     if not os.path.exists(dest_dir):
427         umask = os.umask(00000);
428         os.makedirs(dest_dir, 02775);
429         os.umask(umask);
430     #print "Copying %s to %s..." % (src, dest);
431     if os.path.exists(dest) and os.path.isdir(dest):
432         dest += '/' + os.path.basename(src);
433     # Don't overwrite unless forced to
434     if os.path.exists(dest):
435         if not overwrite:
436             raise file_exists_exc
437         else:
438             if not os.access(dest, os.W_OK):
439                 raise cant_overwrite_exc
440     shutil.copy2(src, dest);
441     os.chmod(dest, perms);
442
443 ################################################################################
444
445 def where_am_i ():
446     res = socket.gethostbyaddr(socket.gethostname());
447     database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname");
448     if database_hostname:
449         return database_hostname;
450     else:
451         return res[0];
452
453 def which_conf_file ():
454     res = socket.gethostbyaddr(socket.gethostname());
455     if Cnf.get("Config::" + res[0] + "::KatieConfig"):
456         return Cnf["Config::" + res[0] + "::KatieConfig"]
457     else:
458         return default_config;
459
460 def which_apt_conf_file ():
461     res = socket.gethostbyaddr(socket.gethostname());
462     if Cnf.get("Config::" + res[0] + "::AptConfig"):
463         return Cnf["Config::" + res[0] + "::AptConfig"]
464     else:
465         return default_apt_config;
466
467 ################################################################################
468
469 # Escape characters which have meaning to SQL's regex comparison operator ('~')
470 # (woefully incomplete)
471
472 def regex_safe (s):
473     s = s.replace('+', '\\\\+');
474     s = s.replace('.', '\\\\.');
475     return s
476
477 ################################################################################
478
479 # Perform a substition of template
480 def TemplateSubst(map, filename):
481     file = open_file(filename);
482     template = file.read();
483     for x in map.keys():
484         template = template.replace(x,map[x]);
485     file.close();
486     return template;
487
488 ################################################################################
489
490 def fubar(msg, exit_code=1):
491     sys.stderr.write("E: %s\n" % (msg));
492     sys.exit(exit_code);
493
494 def warn(msg):
495     sys.stderr.write("W: %s\n" % (msg));
496
497 ################################################################################
498
499 # Returns the user name with a laughable attempt at rfc822 conformancy
500 # (read: removing stray periods).
501 def whoami ():
502     return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '');
503
504 ################################################################################
505
506 def size_type (c):
507     t  = " B";
508     if c > 10240:
509         c = c / 1024;
510         t = " KB";
511     if c > 10240:
512         c = c / 1024;
513         t = " MB";
514     return ("%d%s" % (c, t))
515
516 ################################################################################
517
518 def cc_fix_changes (changes):
519     o = changes.get("architecture", "");
520     if o:
521         del changes["architecture"];
522     changes["architecture"] = {};
523     for j in o.split():
524         changes["architecture"][j] = 1;
525
526 # Sort by source name, source version, 'have source', and then by filename
527 def changes_compare (a, b):
528     try:
529         a_changes = parse_changes(a);
530     except:
531         return -1;
532
533     try:
534         b_changes = parse_changes(b);
535     except:
536         return 1;
537
538     cc_fix_changes (a_changes);
539     cc_fix_changes (b_changes);
540
541     # Sort by source name
542     a_source = a_changes.get("source");
543     b_source = b_changes.get("source");
544     q = cmp (a_source, b_source);
545     if q:
546         return q;
547
548     # Sort by source version
549     a_version = a_changes.get("version", "0");
550     b_version = b_changes.get("version", "0");
551     q = apt_pkg.VersionCompare(a_version, b_version);
552     if q:
553         return q;
554
555     # Sort by 'have source'
556     a_has_source = a_changes["architecture"].get("source");
557     b_has_source = b_changes["architecture"].get("source");
558     if a_has_source and not b_has_source:
559         return -1;
560     elif b_has_source and not a_has_source:
561         return 1;
562
563     # Fall back to sort by filename
564     return cmp(a, b);
565
566 ################################################################################
567
568 def find_next_free (dest, too_many=100):
569     extra = 0;
570     orig_dest = dest;
571     while os.path.exists(dest) and extra < too_many:
572         dest = orig_dest + '.' + repr(extra);
573         extra += 1;
574     if extra >= too_many:
575         raise tried_too_hard_exc;
576     return dest;
577
578 ################################################################################
579
580 def result_join (original, sep = '\t'):
581     list = [];
582     for i in xrange(len(original)):
583         if original[i] == None:
584             list.append("");
585         else:
586             list.append(original[i]);
587     return sep.join(list);
588
589 ################################################################################
590
591 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
592     out = "";
593     for line in str.split('\n'):
594         line = line.strip();
595         if line or include_blank_lines:
596             out += "%s%s\n" % (prefix, line);
597     # Strip trailing new line
598     if out:
599         out = out[:-1];
600     return out;
601
602 ################################################################################
603
604 def validate_changes_file_arg(filename, require_changes=1):
605     """'filename' is either a .changes or .katie file.  If 'filename' is a
606 .katie file, it's changed to be the corresponding .changes file.  The
607 function then checks if the .changes file a) exists and b) is
608 readable and returns the .changes filename if so.  If there's a
609 problem, the next action depends on the option 'require_changes'
610 argument:
611
612  o If 'require_changes' == -1, errors are ignored and the .changes
613                                filename is returned.
614  o If 'require_changes' == 0, a warning is given and 'None' is returned.
615  o If 'require_changes' == 1, a fatal error is raised.
616 """
617     error = None;
618
619     orig_filename = filename
620     if filename.endswith(".katie"):
621         filename = filename[:-6]+".changes";
622
623     if not filename.endswith(".changes"):
624         error = "invalid file type; not a changes file";
625     else:
626         if not os.access(filename,os.R_OK):
627             if os.path.exists(filename):
628                 error = "permission denied";
629             else:
630                 error = "file not found";
631
632     if error:
633         if require_changes == 1:
634             fubar("%s: %s." % (orig_filename, error));
635         elif require_changes == 0:
636             warn("Skipping %s - %s" % (orig_filename, error));
637             return None;
638         else: # We only care about the .katie file
639             return filename;
640     else:
641         return filename;
642
643 ################################################################################
644
645 def real_arch(arch):
646     return (arch != "source" and arch != "all");
647
648 ################################################################################
649
650 def join_with_commas_and(list):
651         if len(list) == 0: return "nothing";
652         if len(list) == 1: return list[0];
653         return ", ".join(list[:-1]) + " and " + list[-1];
654
655 ################################################################################
656
657 def pp_deps (deps):
658     pp_deps = [];
659     for atom in deps:
660         (pkg, version, constraint) = atom;
661         if constraint:
662             pp_dep = "%s (%s %s)" % (pkg, constraint, version);
663         else:
664             pp_dep = pkg;
665         pp_deps.append(pp_dep);
666     return " |".join(pp_deps);
667
668 ################################################################################
669
670 def get_conf():
671         return Cnf;
672
673 ################################################################################
674
675 # Handle -a, -c and -s arguments; returns them as SQL constraints
676 def parse_args(Options):
677     # Process suite
678     if Options["Suite"]:
679         suite_ids_list = [];
680         for suite in split_args(Options["Suite"]):
681             suite_id = db_access.get_suite_id(suite);
682             if suite_id == -1:
683                 warn("suite '%s' not recognised." % (suite));
684             else:
685                 suite_ids_list.append(suite_id);
686         if suite_ids_list:
687             con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
688         else:
689             fubar("No valid suite given.");
690     else:
691         con_suites = "";
692
693     # Process component
694     if Options["Component"]:
695         component_ids_list = [];
696         for component in split_args(Options["Component"]):
697             component_id = db_access.get_component_id(component);
698             if component_id == -1:
699                 warn("component '%s' not recognised." % (component));
700             else:
701                 component_ids_list.append(component_id);
702         if component_ids_list:
703             con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
704         else:
705             fubar("No valid component given.");
706     else:
707         con_components = "";
708
709     # Process architecture
710     con_architectures = "";
711     if Options["Architecture"]:
712         arch_ids_list = [];
713         check_source = 0;
714         for architecture in split_args(Options["Architecture"]):
715             if architecture == "source":
716                 check_source = 1;
717             else:
718                 architecture_id = db_access.get_architecture_id(architecture);
719                 if architecture_id == -1:
720                     warn("architecture '%s' not recognised." % (architecture));
721                 else:
722                     arch_ids_list.append(architecture_id);
723         if arch_ids_list:
724             con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
725         else:
726             if not check_source:
727                 fubar("No valid architecture given.");
728     else:
729         check_source = 1;
730
731     return (con_suites, con_architectures, con_components, check_source);
732
733 ################################################################################
734
735 # Inspired(tm) by Bryn Keller's print_exc_plus (See
736 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
737
738 def print_exc():
739     tb = sys.exc_info()[2];
740     while tb.tb_next:
741         tb = tb.tb_next;
742     stack = [];
743     frame = tb.tb_frame;
744     while frame:
745         stack.append(frame);
746         frame = frame.f_back;
747     stack.reverse();
748     traceback.print_exc();
749     for frame in stack:
750         print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
751                                              frame.f_code.co_filename,
752                                              frame.f_lineno);
753         for key, value in frame.f_locals.items():
754             print "\t%20s = " % key,;
755             try:
756                 print value;
757             except:
758                 print "<unable to print>";
759
760 ################################################################################
761
762 def try_with_debug(function):
763     try:
764         function();
765     except SystemExit:
766         raise;
767     except:
768         print_exc();
769
770 ################################################################################
771
772 # Function for use in sorting lists of architectures.
773 # Sorts normally except that 'source' dominates all others.
774
775 def arch_compare_sw (a, b):
776     if a == "source" and b == "source":
777         return 0;
778     elif a == "source":
779         return -1;
780     elif b == "source":
781         return 1;
782
783     return cmp (a, b);
784
785 ################################################################################
786
787 # Split command line arguments which can be separated by either commas
788 # or whitespace.  If dwim is set, it will complain about string ending
789 # in comma since this usually means someone did 'madison -a i386, m68k
790 # foo' or something and the inevitable confusion resulting from 'm68k'
791 # being treated as an argument is undesirable.
792
793 def split_args (s, dwim=1):
794     if s.find(",") == -1:
795         return s.split();
796     else:
797         if s[-1:] == "," and dwim:
798             fubar("split_args: found trailing comma, spurious space maybe?");
799         return s.split(",");
800
801 ################################################################################
802
803 def Dict(**dict): return dict
804
805 ########################################
806
807 # Our very own version of commands.getouputstatus(), hacked to support
808 # gpgv's status fd.
809 def gpgv_get_status_output(cmd, status_read, status_write):
810     cmd = ['/bin/sh', '-c', cmd];
811     p2cread, p2cwrite = os.pipe();
812     c2pread, c2pwrite = os.pipe();
813     errout, errin = os.pipe();
814     pid = os.fork();
815     if pid == 0:
816         # Child
817         os.close(0);
818         os.close(1);
819         os.dup(p2cread);
820         os.dup(c2pwrite);
821         os.close(2);
822         os.dup(errin);
823         for i in range(3, 256):
824             if i != status_write:
825                 try:
826                     os.close(i);
827                 except:
828                     pass;
829         try:
830             os.execvp(cmd[0], cmd);
831         finally:
832             os._exit(1);
833
834     # Parent
835     os.close(p2cread)
836     os.dup2(c2pread, c2pwrite);
837     os.dup2(errout, errin);
838
839     output = status = "";
840     while 1:
841         i, o, e = select.select([c2pwrite, errin, status_read], [], []);
842         more_data = [];
843         for fd in i:
844             r = os.read(fd, 8196);
845             if len(r) > 0:
846                 more_data.append(fd);
847                 if fd == c2pwrite or fd == errin:
848                     output += r;
849                 elif fd == status_read:
850                     status += r;
851                 else:
852                     fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
853         if not more_data:
854             pid, exit_status = os.waitpid(pid, 0)
855             try:
856                 os.close(status_write);
857                 os.close(status_read);
858                 os.close(c2pread);
859                 os.close(c2pwrite);
860                 os.close(p2cwrite);
861                 os.close(errin);
862                 os.close(errout);
863             except:
864                 pass;
865             break;
866
867     return output, status, exit_status;
868
869 ############################################################
870
871
872 def check_signature (sig_filename, reject, data_filename="", keyrings=None):
873     """Check the signature of a file and return the fingerprint if the
874 signature is valid or 'None' if it's not.  The first argument is the
875 filename whose signature should be checked.  The second argument is a
876 reject function and is called when an error is found.  The reject()
877 function must allow for two arguments: the first is the error message,
878 the second is an optional prefix string.  It's possible for reject()
879 to be called more than once during an invocation of check_signature().
880 The third argument is optional and is the name of the files the
881 detached signature applies to.  The fourth argument is optional and is
882 a *list* of keyrings to use.
883 """
884
885     # Ensure the filename contains no shell meta-characters or other badness
886     if not re_taint_free.match(sig_filename):
887         reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename));
888         return None;
889
890     if data_filename and not re_taint_free.match(data_filename):
891         reject("!!WARNING!! tainted data filename: '%s'." % (data_filename));
892         return None;
893
894     if not keyrings:
895         keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
896
897     # Build the command line
898     status_read, status_write = os.pipe(); 
899     cmd = "gpgv --status-fd %s" % (status_write);
900     for keyring in keyrings:
901         cmd += " --keyring %s" % (keyring);
902     cmd += " %s %s" % (sig_filename, data_filename);
903     # Invoke gpgv on the file
904     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);
905
906     # Process the status-fd output
907     keywords = {};
908     bad = internal_error = "";
909     for line in status.split('\n'):
910         line = line.strip();
911         if line == "":
912             continue;
913         split = line.split();
914         if len(split) < 2:
915             internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line);
916             continue;
917         (gnupg, keyword) = split[:2];
918         if gnupg != "[GNUPG:]":
919             internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg);
920             continue;
921         args = split[2:];
922         if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
923             internal_error += "found duplicate status token ('%s').\n" % (keyword);
924             continue;
925         else:
926             keywords[keyword] = args;
927
928     # If we failed to parse the status-fd output, let's just whine and bail now
929     if internal_error:
930         reject("internal error while performing signature check on %s." % (sig_filename));
931         reject(internal_error, "");
932         reject("Please report the above errors to the Archive maintainers by replying to this mail.", "");
933         return None;
934
935     # Now check for obviously bad things in the processed output
936     if keywords.has_key("SIGEXPIRED"):
937         reject("The key used to sign %s has expired." % (sig_filename));
938         bad = 1;
939     if keywords.has_key("KEYREVOKED"):
940         reject("The key used to sign %s has been revoked." % (sig_filename));
941         bad = 1;
942     if keywords.has_key("BADSIG"):
943         reject("bad signature on %s." % (sig_filename));
944         bad = 1;
945     if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
946         reject("failed to check signature on %s." % (sig_filename));
947         bad = 1;
948     if keywords.has_key("NO_PUBKEY"):
949         args = keywords["NO_PUBKEY"];
950         if len(args) >= 1:
951             key = args[0];
952         reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename));
953         bad = 1;
954     if keywords.has_key("BADARMOR"):
955         reject("ASCII armour of signature was corrupt in %s." % (sig_filename));
956         bad = 1;
957     if keywords.has_key("NODATA"):
958         reject("no signature found in %s." % (sig_filename));
959         bad = 1;
960
961     if bad:
962         return None;
963
964     # Next check gpgv exited with a zero return code
965     if exit_status:
966         reject("gpgv failed while checking %s." % (sig_filename));
967         if status.strip():
968             reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
969         else:
970             reject(prefix_multi_line_string(output, " [GPG output:] "), "");
971         return None;
972
973     # Sanity check the good stuff we expect
974     if not keywords.has_key("VALIDSIG"):
975         reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename));
976         bad = 1;
977     else:
978         args = keywords["VALIDSIG"];
979         if len(args) < 1:
980             reject("internal error while checking signature on %s." % (sig_filename));
981             bad = 1;
982         else:
983             fingerprint = args[0];
984     if not keywords.has_key("GOODSIG"):
985         reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename));
986         bad = 1;
987     if not keywords.has_key("SIG_ID"):
988         reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename));
989         bad = 1;
990
991     # Finally ensure there's not something we don't recognise
992     known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
993                           SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
994                           NODATA="");
995
996     for keyword in keywords.keys():
997         if not known_keywords.has_key(keyword):
998             reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename));
999             bad = 1;
1000
1001     if bad:
1002         return None;
1003     else:
1004         return fingerprint;
1005
1006 ################################################################################
1007
1008 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1009
1010 def wrap(paragraph, max_length, prefix=""):
1011     line = "";
1012     s = "";
1013     have_started = 0;
1014     words = paragraph.split();
1015
1016     for word in words:
1017         word_size = len(word);
1018         if word_size > max_length:
1019             if have_started:
1020                 s += line + '\n' + prefix;
1021             s += word + '\n' + prefix;
1022         else:
1023             if have_started:
1024                 new_length = len(line) + word_size + 1;
1025                 if new_length > max_length:
1026                     s += line + '\n' + prefix;
1027                     line = word;
1028                 else:
1029                     line += ' ' + word;
1030             else:
1031                 line = word;
1032         have_started = 1;
1033
1034     if have_started:
1035         s += line;
1036
1037     return s;
1038
1039 ################################################################################
1040
1041 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1042 # Returns fixed 'src'
1043 def clean_symlink (src, dest, root):
1044     src = src.replace(root, '', 1);
1045     dest = dest.replace(root, '', 1);
1046     dest = os.path.dirname(dest);
1047     new_src = '../' * len(dest.split('/'));
1048     return new_src + src;
1049
1050 ################################################################################
1051
1052 def temp_filename(directory=None, dotprefix=None, perms=0700):
1053     """Return a secure and unique filename by pre-creating it.
1054 If 'directory' is non-null, it will be the directory the file is pre-created in.
1055 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1056
1057     if directory:
1058         old_tempdir = tempfile.tempdir;
1059         tempfile.tempdir = directory;
1060
1061     filename = tempfile.mktemp();
1062
1063     if dotprefix:
1064         filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename));
1065     fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms);
1066     os.close(fd);
1067
1068     if directory:
1069         tempfile.tempdir = old_tempdir;
1070
1071     return filename;
1072
1073 ################################################################################
1074
1075 apt_pkg.init();
1076
1077 Cnf = apt_pkg.newConfiguration();
1078 apt_pkg.ReadConfigFileISC(Cnf,default_config);
1079
1080 if which_conf_file() != default_config:
1081         apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
1082
1083 ################################################################################