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