]> git.decadent.org.uk Git - dak.git/commitdiff
Merge commit 'godog/master' into merge
authorJoerg Jaspert <joerg@debian.org>
Tue, 9 Dec 2008 22:50:20 +0000 (23:50 +0100)
committerJoerg Jaspert <joerg@debian.org>
Tue, 9 Dec 2008 22:50:20 +0000 (23:50 +0100)
* commit 'godog/master':
  drop email.Parser import
  move to tools/
  use different pubDate if going in or out
  use python-debian for .changes parsing
  again, trailing whitespaces removed
  catch and output file write errors
  print help on non-existant dirs
  insert new items on top of feeds and purge them from the bottom
  add datadir option
  trailing whitespaces corrected
  first import

Signed-off-by: Joerg Jaspert <joerg@debian.org>
266 files changed:
.gitignore [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
README [new file with mode: 0644]
config/debian-security/apt.conf [new file with mode: 0644]
config/debian-security/apt.conf.buildd [new file with mode: 0644]
config/debian-security/cron.buildd [new file with mode: 0755]
config/debian-security/cron.daily [new file with mode: 0755]
config/debian-security/cron.unchecked [new file with mode: 0755]
config/debian-security/cron.weekly [new file with mode: 0755]
config/debian-security/dak.conf [new file with mode: 0644]
config/debian-security/vars [new file with mode: 0644]
config/debian/Contents.top [new file with mode: 0644]
config/debian/apt.conf [new file with mode: 0644]
config/debian/apt.conf.buildd [new file with mode: 0644]
config/debian/apt.conf.stable [new file with mode: 0644]
config/debian/cron.buildd [new file with mode: 0755]
config/debian/cron.dinstall [new file with mode: 0755]
config/debian/cron.hourly [new file with mode: 0755]
config/debian/cron.monthly [new file with mode: 0755]
config/debian/cron.unchecked [new file with mode: 0755]
config/debian/cron.weekly [new file with mode: 0755]
config/debian/dak.conf [new file with mode: 0644]
config/debian/extensions.py [new file with mode: 0644]
config/debian/vars [new file with mode: 0644]
config/examples/dak.conf [new file with mode: 0644]
dak/__init__.py [new file with mode: 0644]
dak/check_archive.py [new file with mode: 0755]
dak/check_overrides.py [new file with mode: 0644]
dak/check_proposed_updates.py [new file with mode: 0755]
dak/clean_proposed_updates.py [new file with mode: 0755]
dak/clean_queues.py [new file with mode: 0755]
dak/clean_suites.py [new file with mode: 0755]
dak/compare_suites.py [new file with mode: 0755]
dak/control_overrides.py [new file with mode: 0644]
dak/control_suite.py [new file with mode: 0644]
dak/cruft_report.py [new file with mode: 0755]
dak/dak.py [new file with mode: 0755]
dak/daklib [new symlink]
dak/decode_dot_dak.py [new file with mode: 0644]
dak/examine_package.py [new file with mode: 0755]
dak/find_null_maintainers.py [new file with mode: 0755]
dak/generate_index_diffs.py [new file with mode: 0755]
dak/generate_releases.py [new file with mode: 0755]
dak/import_archive.py [new file with mode: 0755]
dak/import_keyring.py [new file with mode: 0755]
dak/import_ldap_fingerprints.py [new file with mode: 0755]
dak/import_users_from_passwd.py [new file with mode: 0755]
dak/init_db.py [new file with mode: 0755]
dak/init_dirs.py [new file with mode: 0755]
dak/ls.py [new file with mode: 0755]
dak/make_maintainers.py [new file with mode: 0755]
dak/make_overrides.py [new file with mode: 0755]
dak/make_suite_file_list.py [new file with mode: 0755]
dak/mirror_split.py [new file with mode: 0644]
dak/new_security_install.py [new file with mode: 0755]
dak/override.py [new file with mode: 0755]
dak/poolize.py [new file with mode: 0644]
dak/process_accepted.py [new file with mode: 0755]
dak/process_new.py [new file with mode: 0755]
dak/process_unchecked.py [new file with mode: 0755]
dak/queue_report.py [new file with mode: 0755]
dak/reject_proposed_updates.py [new file with mode: 0755]
dak/rm.py [new file with mode: 0755]
dak/show_deferred.py [new file with mode: 0755]
dak/show_new.py [new file with mode: 0755]
dak/split_done.py [new file with mode: 0755]
dak/stats.py [new file with mode: 0755]
dak/test/001/1.dsc [new file with mode: 0644]
dak/test/001/2.dsc [new file with mode: 0644]
dak/test/001/3.dsc [new file with mode: 0644]
dak/test/001/4.dsc [new file with mode: 0644]
dak/test/001/5.dsc [new file with mode: 0644]
dak/test/001/6.dsc [new file with mode: 0644]
dak/test/001/test.py [new file with mode: 0644]
dak/test/002/empty.changes [new file with mode: 0644]
dak/test/002/test.py [new file with mode: 0644]
dak/test/003/krb5_1.2.2-4_m68k.changes [new file with mode: 0644]
dak/test/003/test.py [new file with mode: 0755]
dak/test/004/test.py [new file with mode: 0755]
dak/test/005/bogus-post.changes [new file with mode: 0644]
dak/test/005/bogus-pre.changes [new file with mode: 0644]
dak/test/005/test.py [new file with mode: 0755]
dak/test/005/valid.changes [new file with mode: 0644]
dak/test/006/test.py [new file with mode: 0755]
dak/transitions.py [new file with mode: 0755]
daklib/__init__.py [new file with mode: 0644]
daklib/dak_exceptions.py [new file with mode: 0644]
daklib/database.py [new file with mode: 0755]
daklib/extensions.py [new file with mode: 0644]
daklib/logging.py [new file with mode: 0644]
daklib/queue.py [new file with mode: 0755]
daklib/utils.py [new file with mode: 0755]
debian/changelog [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/postinst [new file with mode: 0644]
debian/python-dep [new file with mode: 0644]
debian/rules [new file with mode: 0755]
docs/NEWS [new file with mode: 0644]
docs/README.assumptions [new file with mode: 0644]
docs/README.config [new file with mode: 0644]
docs/README.first [new file with mode: 0644]
docs/README.new-incoming [new file with mode: 0644]
docs/README.options [new file with mode: 0644]
docs/README.quotes [new file with mode: 0644]
docs/README.stable-point-release [new file with mode: 0644]
docs/THANKS [new file with mode: 0644]
docs/TODO [new file with mode: 0644]
docs/database.dia [new file with mode: 0644]
docs/manpages/Makefile [new file with mode: 0644]
docs/manpages/check-overrides.1.sgml [new file with mode: 0644]
docs/manpages/clean-suites.1.sgml [new file with mode: 0644]
docs/manpages/control-overrides.1.sgml [new file with mode: 0644]
docs/manpages/control-suite.1.sgml [new file with mode: 0644]
docs/manpages/dak.ent [new file with mode: 0644]
docs/manpages/import-users-from-passwd.1.sgml [new file with mode: 0644]
docs/manpages/ls.1.sgml [new file with mode: 0644]
docs/manpages/make-maintainers.1.sgml [new file with mode: 0644]
docs/manpages/override.1.sgml [new file with mode: 0644]
docs/manpages/poolize.1.sgml [new file with mode: 0644]
docs/manpages/process-accepted.1.sgml [new file with mode: 0644]
docs/manpages/process-new.1.sgml [new file with mode: 0644]
docs/manpages/rm.1.sgml [new file with mode: 0644]
docs/transitions.txt [new file with mode: 0644]
scripts/debian/byhand-di [new file with mode: 0755]
scripts/debian/byhand-dm [new file with mode: 0755]
scripts/debian/byhand-tag [new file with mode: 0755]
scripts/debian/byhand-task [new file with mode: 0755]
scripts/debian/copyoverrides [new file with mode: 0755]
scripts/debian/ddtp-i18n-check.sh [new file with mode: 0755]
scripts/debian/dm-monitor [new file with mode: 0755]
scripts/debian/expire_dumps [new file with mode: 0755]
scripts/debian/generate-d-i [new file with mode: 0755]
scripts/debian/import_testing.sh [new file with mode: 0755]
scripts/debian/insert_missing_changedby.py [new file with mode: 0644]
scripts/debian/mkchecksums [new file with mode: 0755]
scripts/debian/mkfilesindices [new file with mode: 0755]
scripts/debian/mklslar [new file with mode: 0755]
scripts/debian/mkmaintainers [new file with mode: 0755]
scripts/debian/stats.R [new file with mode: 0644]
scripts/debian/trigger.daily [new file with mode: 0755]
scripts/debian/update-bugdoctxt [new file with mode: 0755]
scripts/debian/update-ftpstats [new file with mode: 0755]
scripts/debian/update-mailingliststxt [new file with mode: 0755]
scripts/debian/update-mirrorlists [new file with mode: 0755]
scripts/debian/update-pseudopackages.sh [new file with mode: 0755]
scripts/nfu/get-w-b-db [new file with mode: 0755]
setup/add_constraints.sql [new file with mode: 0644]
setup/init_pool.sql [new file with mode: 0644]
setup/init_pool.sql-security [new file with mode: 0644]
src/Makefile [new file with mode: 0644]
src/sql-aptvc.cpp [new file with mode: 0644]
templates/README [new file with mode: 0644]
templates/override.bug-close [new file with mode: 0644]
templates/process-accepted.install [new file with mode: 0644]
templates/process-accepted.unaccept [new file with mode: 0644]
templates/process-new.bxa_notification [new file with mode: 0644]
templates/process-new.prod [new file with mode: 0644]
templates/process-unchecked.accepted [new file with mode: 0644]
templates/process-unchecked.announce [new file with mode: 0644]
templates/process-unchecked.bug-close [new file with mode: 0644]
templates/process-unchecked.bug-experimental-fixed [new file with mode: 0644]
templates/process-unchecked.bug-nmu-fixed [new file with mode: 0644]
templates/process-unchecked.new [new file with mode: 0644]
templates/process-unchecked.override-disparity [new file with mode: 0644]
templates/queue.rejected [new file with mode: 0644]
templates/reject-proposed-updates.rejected [new file with mode: 0644]
templates/rm.bug-close [new file with mode: 0644]
templates/security-install.advisory [new file with mode: 0644]
tools/debianqueued-0.9/ChangeLog [new file with mode: 0644]
tools/debianqueued-0.9/Makefile [new file with mode: 0644]
tools/debianqueued-0.9/PROBLEMS [new file with mode: 0644]
tools/debianqueued-0.9/Queue.README [new file with mode: 0644]
tools/debianqueued-0.9/Queue.README.ravel [new file with mode: 0644]
tools/debianqueued-0.9/Queue.message [new file with mode: 0644]
tools/debianqueued-0.9/README [new file with mode: 0644]
tools/debianqueued-0.9/TODO [new file with mode: 0644]
tools/debianqueued-0.9/changes-template [new file with mode: 0644]
tools/debianqueued-0.9/config [new file with mode: 0644]
tools/debianqueued-0.9/config-security [new file with mode: 0644]
tools/debianqueued-0.9/config-upload [new file with mode: 0644]
tools/debianqueued-0.9/debianqueued [new file with mode: 0755]
tools/debianqueued-0.9/dqueued-watcher [new file with mode: 0755]
tools/debianqueued-0.9/release-num [new file with mode: 0644]
tools/dsync-0.0/COMPILING [new file with mode: 0644]
tools/dsync-0.0/COPYING [new file with mode: 0644]
tools/dsync-0.0/Makefile [new file with mode: 0644]
tools/dsync-0.0/buildlib/archtable [new file with mode: 0644]
tools/dsync-0.0/buildlib/config.guess [new file with mode: 0755]
tools/dsync-0.0/buildlib/config.h.in [new file with mode: 0644]
tools/dsync-0.0/buildlib/config.sub [new file with mode: 0755]
tools/dsync-0.0/buildlib/configure.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/copy.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/debiandoc.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/defaults.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/environment.mak.in [new file with mode: 0644]
tools/dsync-0.0/buildlib/install-sh [new file with mode: 0644]
tools/dsync-0.0/buildlib/inttypes.h.in [new file with mode: 0644]
tools/dsync-0.0/buildlib/library.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/makefile.in [new file with mode: 0644]
tools/dsync-0.0/buildlib/manpage.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/mkChangeLog [new file with mode: 0755]
tools/dsync-0.0/buildlib/program.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/sizetable [new file with mode: 0644]
tools/dsync-0.0/buildlib/staticlibrary.mak [new file with mode: 0644]
tools/dsync-0.0/buildlib/tools.m4 [new file with mode: 0644]
tools/dsync-0.0/buildlib/yodl_manpage.mak [new file with mode: 0644]
tools/dsync-0.0/cmdline/dsync-cdimage.cc [new file with mode: 0644]
tools/dsync-0.0/cmdline/dsync-flist.cc [new file with mode: 0644]
tools/dsync-0.0/cmdline/dsync-flist.h [new file with mode: 0644]
tools/dsync-0.0/cmdline/makefile [new file with mode: 0644]
tools/dsync-0.0/cmdline/path-utils.cc [new file with mode: 0644]
tools/dsync-0.0/configure [new file with mode: 0755]
tools/dsync-0.0/configure.in [new file with mode: 0644]
tools/dsync-0.0/debian/changelog [new file with mode: 0644]
tools/dsync-0.0/debian/compat [new file with mode: 0644]
tools/dsync-0.0/debian/control [new file with mode: 0644]
tools/dsync-0.0/debian/postinst [new file with mode: 0755]
tools/dsync-0.0/debian/rules [new file with mode: 0755]
tools/dsync-0.0/debian/shlibs.local [new file with mode: 0644]
tools/dsync-0.0/debian/substvars [new file with mode: 0644]
tools/dsync-0.0/doc/dsync-flist.1.yo [new file with mode: 0644]
tools/dsync-0.0/doc/examples/dsync.conf [new file with mode: 0644]
tools/dsync-0.0/doc/filelist.sgml [new file with mode: 0644]
tools/dsync-0.0/doc/makefile [new file with mode: 0644]
tools/dsync-0.0/libdsync/compare.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/compare.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/bitmap.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/bitmap.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/cmndline.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/cmndline.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/configuration.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/configuration.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/error.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/error.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/fileutl.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/fileutl.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/md4.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/md4.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/md5.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/md5.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/mmap.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/mmap.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/slidingwindow.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/slidingwindow.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/strutl.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/strutl.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/contrib/system.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/filefilter.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/filefilter.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/filelist.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/filelist.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/filelistdb.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/filelistdb.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/genfilelist.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/genfilelist.h [new file with mode: 0644]
tools/dsync-0.0/libdsync/makefile [new file with mode: 0644]
tools/dsync-0.0/libdsync/rsync-algo.cc [new file with mode: 0644]
tools/dsync-0.0/libdsync/rsync-algo.h [new file with mode: 0644]
tools/dsync-0.0/test/fftest.cc [new file with mode: 0644]
tools/dsync-0.0/test/makefile [new file with mode: 0644]
tools/dsync-0.0/test/pathtest.cc [new file with mode: 0644]
tools/queue_rss.py
web/dinstall.html [new file with mode: 0644]
web/style.css [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f3d74a9
--- /dev/null
@@ -0,0 +1,2 @@
+*.pyc
+*~
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..a43ea21
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+       Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..7ea49de
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,5784 @@
+2008-12-09  Filippo Giunchedi <filippo@debian.org>
+
+       * tools/queue_rss.py: Added, generates RSS feeds of NEW.
+
+2008-12-09  Philipp Kern  <pkern@debian.org>
+
+       * daklib/queue.py (cross_suite_version_check): add an additional
+       parameter to specify if an upload is sourceful or not; do not reject
+       uploads that do not satisfy the "must be newer than" criteria and
+       are binary-only
+       * daklib/queue.py (check_source_against_db, check_binary_against_db):
+       invoke cross_suite_version_check as above
+
+2008-12-04  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (recheck): call reject for
+       Upload.check_{binary,source}_against_db with an empty prefix to not
+       reject on warnings
+
+2008-11-30  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_unchecked.py (do_stableupdate, do_oldstableupdate):
+       move files to NEW for {old,}stable-proposed-updates world-readable
+       (Closes: #368056)
+
+2008-11-30  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/apt.conf: Lets generate experimental content
+       files.
+
+       * dak/daklib/queue.py (Upload.cross_suite_version_check): Add a
+       few () to make cross_suite_version_check finally work as
+       intended (well, we hope). Thanks to Philipp Kern for spotting this.
+
+2008-11-28  Mark Hymers  <mhy@debian.org>
+
+       * dak/new_security_install.py: Don't attempt to delete the .changes files
+       which have already been moved to queue/done by now.
+
+2008-11-27  Mark Hymers  <mhy@debian.org>
+
+       * dak/new_security_install.py: Attempt to tidy up the buildd queue.  The
+       buildd team believes that the fact that the packages are in the security
+       pool after this point should be good enough.
+
+2008-11-25  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_unchecked.py (queue_(un)embargo): (Hopefully) enable
+       sending mails again, which got broken when testing-security
+       handling was (not completly correctly) implemented. Closes: #440004
+
+2008-11-24  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_unchecked.py (check_signed_by_key): remove the
+       binary-upload restriction stuff. unused.
+
+       * config/debian-security/apt.conf.buildd: byebye sarge
+
+       * scripts/nfu/get-w-b-db: remove m68k
+
+       * scripts/debian/update-ftpstats: remove m68k
+
+       * config/debian/dak.conf: remove m68k
+       remove the binary-upload-restrictions
+
+       * config/debian/vars (archs): Remove m68k
+
+2008-11-23  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_unchecked.py (check_files): fix this armel mess
+
+       * config/debian-security/dak.conf: Set ValidTime
+
+       * config/debian-security/cron.weekly: Added
+
+2008-11-17  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/security-install.py: Removed, no longer in use.
+
+2008-11-16  Joerg Jaspert  <joerg@debian.org>
+
+       * scripts/debian/mkfilesindices: useless typical, doesnt have
+       i18n/ dirs. Force them in (i hope).
+
+2008-11-15  Thomas Viehmann <tv@beamnet.de>
+
+       * dak/show_deferred.py: handle uploads that do not close bugs
+
+2008-11-11  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/dak.conf: good bye oldstable/o-p-u
+
+2008-11-10  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.unchecked: how about us ignoring bugs.d.o
+       down? It's not like it is time critical or something to transfer
+       this stuff, it doesn't hurt to have it there a bit later...
+
+2008-11-08  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.hourly: Create new tracefile format.
+
+2008-11-05  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/new_security_install.py (_do_Approve): This sudo call
+       definitely wants the -H option.
+
+2008-11-01  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_unchecked.py (check_files): Also check the
+       upload_suite for uploads. I guess we could kill the default_suite
+       totally, but for now we just look into the upload_suite one too.
+
+       * config/debian-security/dak.conf: Let DefaultSuite be stable
+
+2008-10-27  Joerg Jaspert  <joerg@debian.org>
+
+       * scripts/debian/mkfilesindices: Remove oldstable
+
+       * config/debian/vars: Remove sarge
+
+       * config/debian/dak.conf: Untouchable o-p-u, until we removed all
+       of sarge and its files.
+
+       * config/debian/apt.conf.oldstable: Removed
+
+       * config/debian/apt.conf: Remove oldstable
+
+2008-10-14  Thomas Viehmann <tv@beamnet.de>
+
+       * dak/show_deferred.py: produce .changes and improve status
+
+2008-10-07  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.dinstall: Only keep the last 60 days of
+       dinstall logfiles on disc.
+
+2008-10-05  Thomas Viehmann <tv@beamnet.de>
+
+        * daklib/database.py: added get_suites
+       * dak/dak.py, dak/show_deferred.py: add show-deferred to dak.
+
+2008-09-23  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/dak.conf: Add the validtime fields, set to 7
+       days.
+
+       * dak/generate_releases.py (main): Add a "Valid-Until" line into
+       our release files, meaning "$receiver shouldn't trust this files
+       after that date". Should be used by apt and similar tools to
+       detect some kind of MITM attacks, see #499897
+
+2008-09-21  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.hourly: Generate the DEFERRED queue
+       overview.
+
+2008-09-13  Philipp Kern  <pkern@debian.org>
+
+       * dak/queue.py (dump_vars): make .dak u,g=rw,o=r; James'
+       assumption (as stated in 2002-05-18's ChangeLog entry)
+       was that people will use the information therein albeit
+       it is "just" a duplication of information present in
+       other control files; people should still not use it
+       as source of information but access to those files makes
+       dak debugging easier and there is no leak of sensitive
+       information involved
+
+2008-09-12  Philipp Kern  <pkern@debian.org>
+
+       * dak/new_security_install.py (actually_upload): remove
+       oldstable-security/amd64 check; Etch, as the next oldstable,
+       already had proper amd64 support
+
+2008-09-12  Joerg Jaspert  <joerg@debian.org>
+
+       * scripts/debian/update-pseudopackages.sh: s/i/file/
+
+2008-09-11  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/pseudo-packages.description, ...maintainers:
+       Removed, now with the bts people
+
+       * scripts/debian/update-pseudopackages.sh: Added, fetching
+       pseudo-packages from new bts location
+
+       * scripts/debian/mkmaintainers: Use new location
+
+2008-09-08  Philipp Kern  <pkern@debian.org>
+
+       * dak/check_archive.py (check_checksums): rewind the files
+       before the sha1sum/sha256sum checks as they got seeked by
+       md5sum
+
+       * daklib/utils.py (build_file_list): do not die on very
+       old dsc files without format header
+
+2008-09-07  Philipp Kern  <pkern@debian.org>
+
+       * daklib/utils.py (check_hash): try..except..finally only
+       works on python >=2.5.
+
+       * dak/process_accepted.py (install): better use dsc_file
+       instead of the (for the loop iteration) static file
+       variable
+
+2008-09-07  Philipp Kern  <pkern@debian.org>
+
+       * daklib/utils.py (check_hash): change the comment and warn
+       if a file is not found when checking the hashes (i.e. when
+       it is probably in the pool)
+
+       * daklib/utils.py (check_size): do not bail out if the file
+       is not found, because it may be in the pool
+
+       * dak/process_accepted.py (install): bail out and skip the
+       upload when ensure_hashes fails, print the rejection messages
+       as warnings
+
+2008-08-28  Philipp Kern  <pkern@debian.org>
+
+       * daklib/utils.py (check_hashes): adapt to different API, check
+       sizes separately
+
+       * daklib/utils.py (parse_changes, parse_deb822): refactor
+       the string-based logic of parse_changes into a new function
+       parse_deb822; parse_changes itself remains file-based
+
+       * daklib/utils.py (hash_key): gives the key of a hash in the
+       files dict
+
+       * daklib/utils.py (create_hash, check_size): made more readable
+
+       * daklib/utils.py (check_hash): just check the hashes and complain
+       about missing checksums
+
+       * daklib/utils.py (check_hash_fields): function to reject unknown
+       checksums fields
+
+       * daklib/utils.py (_ensure_changes_hash, _ensure_dsc_hash): helper
+       functions for ensure_hashes; check their corresponding manifests'
+       hashes
+
+       * daklib/utils.py (ensure_hashes): retrieve the checksums fields
+       from the original filecontents blob so that they do not need to
+       be present in the .dak; refactored the actual checks by calling
+       the aforementioned helper functions
+
+       * daklib/utils.py (parse_checksums): parse a given checksums field
+       in a manifest and insert the values found into the files dict,
+       checking the file sizes on the way
+
+2008-09-06  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (is_source_in_queue_dir): Access the right
+       variable to check if the given entry in the queue is the sourceful
+       upload we are looking for.
+
+2008-09-02  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/pseudo-packages.description: Added debian-i18n and
+       buildd.emdebian.org
+
+       * dak/process_new.py (_accept): Fix Philipps new helper function
+       to not break by moving Upload.build_summaries there.
+
+2008-08-31  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (_accept): new helper function to accept
+       an upload regularly, obeying no-action if set
+       * dak/process_new.py (do_accept): use _accept
+       * dak/process_new.py (do_accept_stableupdate): bail out in else
+       on binary uploads, in case we missed something; use the _accept
+       helper
+
+2008-08-30  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (is_source_in_queue_dir): join the queue path
+       because os.listdir entries come with their path stripped
+
+2008-08-30  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (do_accept_stableupdate): state what we intend
+       to do
+
+2008-08-26  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (is_source_in_queue_dir): fix variable usage
+       * dak/process_new.py (move_to_holding): just state what we intend
+       to do in no-action mode
+       * dak/process_new.py (do_accept_stableupdate): fetch summaries,
+       fix invokation of is_source_in_queue_dir, actually accept sourceful
+       uploads in p-u holding
+
+2008-08-26  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (do_accept): do not try to free the unchecked
+       lockfile in no-action mode
+
+2008-08-16  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.dinstall: We dont want i18n to ever fail
+       dinstall, add a || true
+
+2008-08-15  Mark Hymers  <mhy@debian.org>
+
+       * daklib/utils.py: Actually import a module before using it.
+
+       * daklib/utils.py: Actually check we have basedict before trying to
+       use it.
+
+       *  dak/process_accepted.py, dak/process_unchecked.py,
+       daklib/database.py: Don't change get_files_id to use sha1sum and
+       sha256sum.
+
+       * setup/init_pool.sql, dak/check_archive.py, dak/decode_dot_dak.py,
+       dak/process_accepted.py, dak/process_unchecked.py, daklib/database.py,
+       daklib/queue.py, daklib/utils.py: Attempt to add sha1sum and
+       sha256sums into the database.  The complication is that we have to
+       keep backwards compatibility with the .dak files already in existance.
+       Note that import_archive hasn't been hacked to deal with this yet.
+
+2008-08-14  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.dinstall: Added the i18n retrieval of package
+       description translations
+
+2008-08-12  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.dinstall: Complicate the i18n export a little
+       by using date/hour based directories which we then link into the
+       web view. They contain a signed timestamp file now, which means
+       the i18n people can take a long time to generate files, yet we
+       still know exactly on which dataset their data is based on, and
+       can then verify it with that. Ensures we only get descriptions for
+       packages we know off (or knew of in the past 2 days).
+
+2008-08-11  Joerg Jaspert  <joerg@debian.org>
+
+       * web/dinstall.html: Added
+
+       * config/debian/dak.conf: Added back the pgp keyrings for now, as
+       it seems that we should keep it for a few more days, until we
+       somehow got ll those oldtimers to get a newer key into the
+       keyring. Unfortunately our logic to look for uploads done from
+       that keyring wasnt the most perfect one, so well, it is actually
+       used. Damn.
+
+2008-08-09  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/dak.conf: No longer use the pgp keyring - no
+       uploads recorded for any of the pgp keys for a long time.
+
+       * config/debian/cron.dinstall: Export the i18n foo.
+
+2008-08-08  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.dinstall: Create a hardlinked tree of the
+       ftp/ in mirror/ so we have more atomic mirror updates for the
+       buildds
+
+       * config/debian/cron.unchecked: Added signing of buildd incoming
+
+2008-08-07  Philipp Kern  <pkern@debian.org>
+
+       * dak/process_new.py (do_accept): handle uploads to (oldstable-)
+         proposed-updates differently and put them into p-u holding
+         for review instead of unconditionally accepting them into
+         p-u proper; additional care needed to be taken to look
+         out for the source if a binary-only upload is being handled
+
+2008-08-07  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/cruft_report.py (parse_nfu): call utils.warn instead of warn
+       (main): Only do the nfu stuff if nfu is a check we want to run
+       later.
+       (main): And another place where we dont want to do nfu foo unless
+       we need nfu
+
+       * dak/make_suite_file_list.py (main): Fix a bug that has been
+       there for ages, but "just" never triggered.
+
+2008-08-07  Stephen Gran  <sgran@debian.org>
+
+       * Drop use of exec to eval variable interpolation
+2008-08-07  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_accepted.py (install): Error out with the new
+       exception if we dont know the source package for a file we want to
+       install. Shouldn't ever hit us, but better safe than sorry.
+
+       * daklib/dak_exceptions.py (dakerrors): new exception - no source field.
+
+2008-08-05  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.unchecked: disable the ssh-move insanity (and
+       as soon as rietz is back online - replace it with a one-line scp
+       or rsync statement followed by a one-line rm)
+       And now replaced this 128 line perl "not-invented-here" with a
+       one-line rsync command, using a feature rsync only understands
+       since sarge - damn new-fangled technology.
+
+2008-08-05  Joachim Breitner <nomeata@debian.org>
+
+       * dak/cruft_report.py: In full mode, report out-of-date binaries on
+       architectures that have set Not-For-Us for that package.
+
+       * scripts/nfu/get-w-b-db: Script to fetch the wanna-build database
+       dump from http://buildd.debian.org/
+
+       * config/debian/cron.weekly: Run the above script
+
+2008-08-03  Mark Hymers <mhy@debian.org>
+
+       * dak/process_new.py: Apply jvw's patch so that process_new shows
+       packages which need binary uploads sorted earlier than other packages.
+
+2008-07-26  Joerg Jaspert  <joerg@debian.org>
+
+       * templates/reject-proposed-updates.rejected,dak/reject_proposed_updates.py:
+       applied a patch by luk modifying the stable rejection mails to fit
+       reality a bit more
+
+       * config/debian/dak.conf: no m68k in testing, so no m68k in t-p-u
+       r4 now
+
+2008-06-19  Thomas Viehmann  <tv@beamnet.de>
+
+       * dak/process_unchecked.py (check_dsc,check_hashes): Catch
+       UnknownFormatError and reject
+
+2008-06-15  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.weekly: Work around a git bug until git is
+       fixed upstream and the fix is on backports.org
+
+       * config/debian/cron.dinstall: (various ssh calls): Make them use
+       batchmode/connect/setuptimeout to not take too long with
+       connections... Also || true them, no need to die in dinstall if
+       one host isn't reachable.
+       Also do not die when the ldap server is unreachable, just ignore
+       that error.
+
+       * README: Updated mailing list location
+
+2008-06-14  Otavio Salvador  <otavio@debian.org>
+
+       * docs/manpages/clean-suites.1.sgml: Minor typo fix
+
+       * dak/import_archive.py: Add support to udeb packages
+
+       * dak/control_suite.py (main): Handle SystemError exception in
+       case of a incompatible commandline parameter
+
+       * dak/check_overrides.py (main): Use case-insensitive comparing
+       for codename
+
+2008-06-14  Joerg Jaspert  <joerg@debian.org>
+
+       * scripts/debian/byhand-task: Merged patch from Frans Pop to
+       fail on byhand-task uploads if they do not go to unstable.
+
+       * config/debian/cron.weekly: Do a little git cleanup work too.
+
+       * config/debian/cron.buildd: Add batchmode and also
+       Connect/SetupTimeout parameters to ssh
+
+       * config/debian/cron.dinstall (POSTDUMP): Compress all
+       uncompressed psql backups
+
+2008-06-08  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_unchecked.py (check_urgency): Lowercase urgency
+       before we (eventually) warn on it. Patch taken from Russ Allbery.
+
+2008-06-01  Otavio Salvador  <otavio@debian.org>
+
+       * daklib/queue.py (check_valid): allow debian-installer specific
+       sources to have 'debian-installer' section.
+
+2008-05-28  Frans Pop  <fjp@debian.org>
+
+       * add autobyhand support for task overrides (from tasksel)
+
+2008-05-27  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/pseudo-packages.maintainers: Change ftp.debian.org
+       pseudopackage maintainer name.
+
+2008-05-12  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/transitions.py: use yaml.dump instead of syck.dump, as syck
+       seems to have a bug in its dump(), causing it to write illegal entries
+       And also do this for load.
+
+2008-05-10  Stephen Gran   <sgran@debian.org>
+       * tools/debianqueued-0.9/debianqueued: First pass at a send_mail
+         implementation that sucks less
+       * Update debian/control to reflect new perl dependency
+
+2008-05-09  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/override.py (main): substitute value in X-Debian-Package
+       header
+
+       * templates/override.bug-close: Add X-Debian-Package header
+       * templates/reject-proposed-updates.rejected: dito
+       * templates/queue.rejected: dito
+       * templates/process-unchecked.new: dito
+       * templates/process-unchecked.bug-nmu-fixed: dito
+       * templates/process-unchecked.bug-experimental-fixed: dito
+       * templates/process-unchecked.bug-close: dito
+       * templates/process-unchecked.announce: dito
+       * templates/process-unchecked.accepted: dito
+       * templates/process-new.prod: dito
+       * templates/process-accepted.unaccept: dito
+       * templates/process-accepted.install: dito
+       * templates/process-unchecked.override-disparity: dito
+
+2008-05-08  Joerg Jaspert  <joerg@debian.org>
+
+       * templates/override.bug-close: Add X-Debian header
+       * templates/rm.bug-close: dito
+       * templates/reject-proposed-updates.rejected: dito
+       * templates/queue.rejected: dito
+       * templates/process-unchecked.new: dito
+       * templates/process-unchecked.bug-nmu-fixed: dito
+       * templates/process-unchecked.bug-experimental-fixed: dito
+       * templates/process-unchecked.bug-close: dito
+       * templates/process-unchecked.announce: dito
+       * templates/process-unchecked.accepted: dito
+       * templates/process-new.prod: dito
+       * templates/process-accepted.unaccept: dito
+       * templates/process-accepted.install: dito
+       * templates/process-unchecked.override-disparity: dito, but also
+       mention that people should include the package lists with the
+       override disparities.
+
+2008-05-06  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.dinstall: Put the timestamp stuff into an own
+       function, call that from everywhere. Also change the timestamp
+       format to not be local dependent.
+
+2008-05-05  Joerg Jaspert  <joerg@debian.org>
+
+       * daklib/dak_exceptions.py (dakerrors): add TransitionsError
+       * dak/transitions.py: Use it, instead of the own definition
+
+2008-05-05  Mark Hymers  <mhy@debian.org>
+
+       * daklib/dak_exceptions.py: Add a default message and tidy up our string
+       representation
+
+2008-05-05  Joerg Jaspert  <joerg@debian.org>
+
+       * daklib/dak_exceptions.py: New file, central place for all those
+       own exceptions dak may raise.
+
+       * daklib/utils.py: Use dak_exceptions and delete all those string
+       exception raising stuff, which is depcreated.
+       During that delete the unknown_hostname_exc, as it wasnt used.
+
+       * dak/import_archive.py: use the new Exception class
+       * dak/rm.py: dito
+       * dak/generate_releases.py: dito
+       * dak/queue_report.py: dito
+       * daklib/queue.py: dito
+
+2008-05-04  Joerg Jaspert  <joerg@debian.org>
+
+       * daklib/queue.py: Various pychecker cleanups
+
+       * dak/import_keyring.py: Remove unused daklib.logging and Logger
+       and add the actually used daklib/utils
+
+       * dak/examine_package.py: remove daklib.queue import, never used
+
+       * dak/check_proposed_updates.py: Import stuff from daklib as
+       "import daklib.foo as foo"
+       * dak/clean_proposed_updates.py: likewise
+       * dak/clean_queues.py: likewise
+       * dak/clean_suites.py: likewise
+       * dak/compare_suites.py: likewise
+       * dak/cruft_report.py: likewise
+       (get_suite_binaries): Seperated in own function, out of main. More
+       of main should be splitted. (Or well, cruft_report redesigned a
+       little, so its easier to run on multiple suites with differing tests)
+
+       * dak/examine_package.py: likewise
+       * dak/find_null_maintainers.py: likewise
+       * dak/generate_index_diffs.py: likewise
+       * dak/generate_releases.py: likewise
+       * dak/import_archive.py: likewise
+       * dak/import_ldap_fingerprints.py: likewise
+       * dak/import_users_from_passwd.py: likewise
+       * dak/init_db.py: likewise
+       * dak/init_dirs.py: likewise
+       * dak/ls.py: likewise
+       * dak/make_maintainers.py: likewise
+       * dak/make_overrides.py: likewise
+       * dak/make_suite_file_list.py: likewise
+       * dak/new_security_install.py: likewise
+       * dak/override.py: likewise
+       * dak/process_accepted.py: likewise
+       * dak/process_new.py: likewise
+       * dak/process_unchecked.py: likewise
+       * dak/rm.py: likewise
+       * dak/show_new.py: likewise
+       * dak/split_done.py: likewise
+       * dak/stats.py: likewise
+       * dak/transitions.py: likewise
+
+       * dak/check_archive.py (check_files_not_symlinks): Remove
+       long-time unused and commented code. Import stuff from daklib as
+       "import daklib.foo as foo"
+
+2008-05-04  Thomas Viehmann  <tv@beamnet.de>
+
+       * dak/process_unchecked.py (check_signed_by_key): cater for uid_email
+       None in sponsor notification
+
+2008-05-03  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/examine_package.py: clean up pychecker warnings (merged with
+       Thomas changes to the NEW display)
+
+2008-05-03  Mark Hymers <mhy@debian.org>
+
+       * dak/check_archive.py: clean up pychecker warnings
+       * dak/check_overrides.py: likewise
+       * dak/check_proposed_updates.py: likewise
+       * dak/clean_proposed_updates.py: likewise
+       * dak/clean_queues.py: likewise
+       * dak/control_overrides.py: likewise
+       * dak/control_suite.py: likewise
+       * dak/decode_dot_dak.py: likewise
+       * dak/examine_package.py: likewise
+       * dak/process_new.py: likewise
+       * dak/process_unchecked.py: likewise
+       * dak/queue_report.py: likewise
+       * dak/reject_proposed_updates.py: likewise
+       * dak/security_install.py: likewise
+       * dak/show_new.py: likewise
+       * dak/stats.py: likewise
+       * dak/symlink_dists.py: likewise
+       * dak/transitions.py: likewise
+>>>>>>> sec-merge:ChangeLog
+
+2008-05-03  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/cron.daily: Rename to
+       * config/debian/cron.dinstall: this, as its not really something run
+       daily for some time now. And while dinstall is an OLD name, its
+       recognized pretty well within Debian
+       Also add some more timing information
+       Do not allow automated [o-]p-u-new processing to break dinstall
+
+2008-05-03  Thomas Viehmann  <tv@beamnet.de>
+
+       * web/,web/style.css: add web dir and current style.css
+
+       * dak/examine_package.py, dak/show_new.py: improve NEW html pages,
+       based on mock-up by M. Ferrari.
+       remove Checksums-* from examine-package output
+
+2008-05-03  Thomas Viehmann  <tv@beamnet.de>
+
+       * dak/process_unchecked.py (check_hashes): Reject on error while
+       parsing Checksums-*.
+
+2008-05-02  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/pseudo-packages*: Removed listarchives, closes #468667
+       added wiki.debian.org (currently pointing to debian-www), closes #194206
+       added security-tracker, closes #436152
+       added release.debian.org, closes #451005
+
+       * config/debian/cron.buildd: Cleanup unreachable code. (If someone
+       still wants it - its in revision control...)
+
+       * config/debian/cron.daily: Modify call to update-ftpstats to only
+       use files log/2*, instead of log/*, so it ignores the new cron/
+       subdir. Makes sure it can't get confused, and is also safe for
+       nearly thousand years. If this code is really in use in year 3000,
+       im sure people can adjust it! :)
+
+       * config/debian/vars: Add logdir as a place for cronjob log output
+
+       * config/debian/cron.daily: Use a logfile and be more verbose of
+       whats going on.
+         Also moved the commented VACUUM ; VACUUM ANALYZE calls over to
+       cron.weekly, ...
+       * config/debian/cron.weekly: likewise,
+         ... and activate those calls again. Once a week, as an
+       additional safety bet to be sure the stuff does get run is ok,
+       even if we have autovacuum by default.
+
+2008-05-02  Thomas Viehmann  <tv@beamnet.de>
+
+       * dak/process_unchecked.py (check_hashes): fix typo in
+         checksum reject message.
+
+2008-05-02  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/extensions.py: used reindent.py from the python
+       2.x source examples to get all dak code use the same indentation
+       style, no longer a mix of 4 spaces / 1 tab.
+       * dak/check_archive.py: likewise
+       * dak/check_overrides.py: likewise
+       * dak/check_proposed_updates.py: likewise
+       * dak/clean_proposed_updates.py: likewise
+       * dak/clean_queues.py: likewise
+       * dak/clean_suites.py: likewise
+       * dak/compare_suites.py: likewise
+       * dak/control_overrides.py: likewise
+       * dak/control_suite.py: likewise
+       * dak/cruft_report.py: likewise
+       * dak/dak.py: likewise
+       * dak/decode_dot_dak.py: likewise
+       * dak/examine_package.py: likewise
+       * dak/find_null_maintainers.py: likewise
+       * dak/generate_index_diffs.py: likewise
+       * dak/generate_releases.py: likewise
+       * dak/import_archive.py: likewise
+       * dak/import_keyring.py: likewise
+       * dak/import_ldap_fingerprints.py: likewise
+       * dak/import_users_from_passwd.py: likewise
+       * dak/init_db.py: likewise
+       * dak/init_dirs.py: likewise
+       * dak/ls.py: likewise
+       * dak/make_maintainers.py: likewise
+       * dak/make_overrides.py: likewise
+       * dak/make_suite_file_list.py: likewise
+       * dak/mirror_split.py: likewise
+       * dak/new_security_install.py: likewise
+       * dak/override.py: likewise
+       * dak/poolize.py: likewise
+       * dak/process_accepted.py: likewise
+       * dak/process_new.py: likewise
+       * dak/process_unchecked.py: likewise
+       * dak/queue_report.py: likewise
+       * dak/reject_proposed_updates.py: likewise
+       * dak/rm.py: likewise
+       * dak/security_install.py: likewise
+       * dak/show_new.py: likewise
+       * dak/split_done.py: likewise
+       * dak/stats.py: likewise
+       * dak/symlink_dists.py: likewise
+       * dak/test/001/test.py: likewise
+       * dak/test/002/test.py: likewise
+       * dak/transitions.py: likewise
+       * daklib/extensions.py: likewise
+       * daklib/logging.py: likewise
+       * daklib/queue.py: likewise
+       * daklib/utils.py: likewise
+       * scripts/debian/insert_missing_changedby.py: likewise
+
+       * dak/process_new.py (recheck): Make the path check more robust,
+       so we no longer have to keep process_new seperate trees between
+       security and normal archive.
+
+2008-04-27  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_accepted.py (Urgency_Log.__init__): Warn if the
+       configured path does not exist or is not writeable by us. Use a
+       tmp path if so, so we do not lose the urgencies in such cases.
+
+       * config/debian/dak.conf: Changed path for UrgencyLog
+       Same for the ReleaseTransitions file
+
+       * config/debian/cron.daily: Notify qa user on merkel of dinstall
+       start, Remove the britney call
+
+2008-04-26  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_new.py: Call end() whenever we try to leave by
+       pressing Q
+
+       * config/debian/cron.daily: Also report NBS in experimental
+
+2008-04-25  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/cruft_report.py (main): Make it possible to look at
+       experimental too, especially NBS
+
+       * dak/split_done.py (main): Only move files into their subdirs if
+       they are older than 30 days. That enables us to run this script as
+       part of a cronjob.
+
+       * config/debian/cron.weekly: Run dak split-done
+
+2008-04-23  Thomas Viehmann  <tviehmann@debian.org>
+
+       * dak/process_unchecked.py: add changes["sponsoremail"]
+         for sponsored uploads if desired
+       * daklib/queue.py: add changes["sponsoremail"] to
+         Subst["__MAINTAINER_TO__"] if present
+       * daklib/utils.py: add functions
+         is_email_alias to check which accounts allow email forwarding,
+         which_alias_file to find the alias file, and
+         gpg_get_key_addresses to find uid addresses for a given
+           fingerprint
+
+2008-04-22  Joerg Jaspert  <joerg@debian.org>
+
+       * setup/init_pool.sql: added a function/aggregate for the release
+       team to base some script on it.
+
+       * config/debian/cron.daily: push katie@merkel to immediately start
+       the sync of projectb there.
+
+2008-04-21  Joerg Jaspert  <joerg@debian.org>
+
+       * scripts/debian/expire_dumps: New script, expires old database
+       dumps, using a scheme to keep more of the recent dumps.
+
+       * config/debian/cron.daily: Use the new script. Also - compress
+       all files older than 7 days, instead of 30.
+
+       * dak/process_accepted.py (install): Do not break if a
+       source/maintainer combination is already in src_uploaders, "just"
+       warn us.
+
+2008-04-20  Thomas Viehmann  <tviehmann@debian.org>
+
+       * daklib/utils.py (build_file_list): Deal with "Format 3 style"
+       Format lines (ie. those having extra text appended).
+
+2008-04-19  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_unchecked.py (check_files): Sanity check the
+       provides field, which closes #472783
+
+2008-04-18  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/dak.conf: Add mapping stable-proposed-updates
+         -> proposed-updates.
+
+       * dak/transitions.py (load_transitions): Additionally check for
+       invalid package list indentation
+
+2008-04-17  Joerg Jaspert  <joerg@debian.org>
+
+       * config/debian/dak.conf: Add TempPath statement for the Release
+       Transitions script
+
+       * dak/transitions.py (temp_transitions_file): Use the TempPath
+       (write_transitions_from_file): Check if the file we should get our
+       transitions from is in our TempPath, error out if it isnt
+       (main): Check for TempPath existance
+
+2008-04-12  James Troup  <troup@debian.org>
+
+       * dak/clean_proposed_updates.py: add support for -s/--suite and
+       -n/--no-action.
+
+2008-04-11  Anthony Towns  <ajt@debian.org>
+
+       * dak/utils.py: build_file_list() extra parameters so it can
+       build a file list for checksums-foo fields. Don't use float() to
+       compare formats, because Format: 1.10 should compare greater than
+       Format: 1.9 (use "1.9".split(".",1) and tuple comparison instead)
+
+       * dak/process_unchecked.py: check_md5sum becomes check_hashes
+       and check_hash. If changes format is 1.8 or later, also check
+       checksums-sha1 and checksums-sha256 for both .changes and .dsc,
+       and reject on presence/absence of un/expected checksums-* fields.
+
+2008-04-07  Joerg Jaspert  <joerg@debian.org>
+
+       * daklib/utils.py (build_file_list): Check for dpkg .changes
+       adjusted to reject newer (and right now broken) 1.8 version, until
+       dpkg (or debsign) is fixed and doesn't produce invalid .changes anymore
+
+2008-03-22  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/transitions.py (load_transitions): Check if all our keys are
+       defined, if there are only keys defined we want and also the types
+       of the various keys.
+
+2008-03-22  Anthony Towns  <ajt@debian.org>
+
+       * dak/edit_transitions.py: Add --import option.
+       Add --use-sudo option. Use fcntl locking for writing.
+       Move writing into a function (write_transitions).
+       Reinvoke self using sudo and --import if necessary.
+       Move temporary file creation into a function, use mkstemp.
+       Rename to "dak transitions".
+
+2008-03-21  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/edit_transitions.py (edit_transitions): Use sudo to copy the
+       edited file back in place
+       (check_transitions): Use proper locking and also use sudo to copy
+       the new file in place
+
+2008-03-21  Anthony Towns <ajt@debian.org>
+
+       * config/debian/extensions.py: Add infrastructure for replacing
+       functions in dak modules; add upload blocking for dpkg.
+
+2008-03-12  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/edit_transitions.py: Done a number of cleanups to make code
+       working. Also changed the way prompting/answering goes, to not
+       have to import daklib/queue.
+       (edit_transitions): When done with a successful edit - also print
+       a final overview about defined transitions
+
+2008-03-11  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/process_unchecked.py: Import syck module directly, not "from
+       syck import *"
+       (check_transition): Do the check for sourceful upload in here
+       Also adjust the syck loading commands, rename new_vers to
+       expected, curvers to current, to make it more clear what they mean.
+
+       * daklib/database.py (get_suite_version): Renamed from
+       get_testing_version. Also changed the cache variables name
+
+       * The above changes are based on modifications from Anthony.
+
+       * dak/dak.py (init): Renamed check -> edit transitions
+
+       * dak/edit_transitions.py: Renamed from check_transitions.py
+       (main): Also rename new_vers/curvers to expected/current
+       Basically a nice rewrite, so it now does checks and edit,
+       depending on how you call it. Check also removes old transitions,
+       if user wants it.
+
+2008-03-02  Joerg Jaspert  <joerg@debian.org>
+
+       * debian/control (Suggests): Add python-syck to Depends:
+
+       * dak/dak.py (init): Tell it about check_transitions
+
+       * dak/check_transitions.py (usage): Added, checks the transitions
+       file (if any)
+
+       * daklib/database.py (get_testing_version): Added. Returns the
+       version for the source in testing, if any
+
+       * dak/process_unchecked.py (check_transition): Added. Checks if a
+       release team member defined a transition, and rejects based on
+       that data.
+       (process_it): Use it.
+       (check_transition): Warn on broken transitions file and return,
+       not doing anything.
+       (check_transition): Moved out of here, into daklib/queue
+       (process_it): Call check_transitions only if
+       changes[architecture] has source included.
+       (check_transition): Now call the database.get_testing_version
+
+2008-02-09  Christoph Berg <myon@debian.org>
+
+       * daklib/queue.py (get_type): fubar does not exist in global
+       namespace.
+
+       * setup/add_constraints.sql setup/init_pool.sql: Add changedby column
+       to source table, and move src_uploaders after source so the REFERNCES
+       clause works.
+       * dak/process_accepted.py (install): Fill the changedby column from
+       the information found in the .changes. This will allow to identify
+       NMUs and sponsored uploads more precisely in tools querying projectb.
+       * scripts/debian/insert_missing_changedby.py: Script to import yet
+       missing fields from filippo's uploads-history DB.
+
+2008-02-06  Joerg Jaspert  <joerg@debian.org>
+
+       * daklib/utils.py (check_signature): Make variable key available,
+       so we can access it.
+
+2008-01-07  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/examine_package.py (check_deb): Remove linda call. It
+       provides no added benefit to lintian anymore.
+
+2008-01-07  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/examine_package.py (check_deb): Remove linda call. It
+       provides no added benefit to lintian anymore.
+
+2008-01-06  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/examine_package.py (do_lintian): lintian now supports html
+       coloring, so use it.
+       (do_command): Dont escape html chars if param escaped = 1
+
+2008-01-06  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/examine_package.py (do_lintian): lintian now supports html
+       coloring, so use it.
+       (do_command): Dont escape html chars if param escaped = 1
+
+2007-12-31  Anthony Towns  <ajt@debian.org>
+
+       * dak/process_new.py (recheck): pass "" for prefix_str to reject()
+       when processing result of check_dsc_against_db so we don't promote
+       warnings to rejections.
+
+2007-12-31  Anthony Towns  <ajt@debian.org>
+
+       * dak/process_new.py (recheck): pass "" for prefix_str to reject()
+       when processing result of check_dsc_against_db so we don't promote
+       warnings to rejections.
+
+2007-12-30  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/dak.py (init): add show-new. This is based on a patch
+       submitted by Thomas Viehmann in Bug #408318, but large parts of
+       handling it are rewritten and show-new is done by me.
+
+       * dak/queue_report.py (table_row): Add link to generated html page
+       for NEW package.
+
+       * dak/show_new.py: new file, generates html overview for NEW
+       packages, similar to what we see with examine-package.
+
+       * config/debian/cron.hourly: Add show-new call
+
+       * config/debian/dak.conf: Add HTMLPath for Show-New
+
+       * dak/examine_package.py (print_copyright): ignore stderr when
+       finding copyright file.
+       (main): add html option
+       (html_escape): new function
+       (escape_if_needed): ditto
+       (headline): ditto
+       (colour_output): ditto
+       (print_escaped_text): ditto
+       (print_formatted_text): ditto
+       - use those functions everywhere where we generate output, as they
+       easily know if we want html or not and just DTRT
+       (do_lintian): new function
+       (check_deb): use it
+       (output_deb_info): Use print_escaped_text, not print_formatted_text.
+       Also import daklib.queue, determine_new now lives there
+
+       Also add a variable to see if we want html output. Default is
+       disabled, show_new enables it for its use.
+       Most of html, besides header/footer are in examine_package instead
+       of show_new, as it makes it a whole lot easier to deal with it at
+       the point the info is generated.
+
+
+       * dak/process_new.py (determine_new): Moved out of here.
+       (check_valid): Moved out of here.
+       (get_type): Moved out of here.
+
+       * daklib/queue.py (determine_new): Moved here.
+       (check_valid): Moved here.
+       (get_type): Moved here.
+
+       * dak/init_db.py (do_section): Remove non-US code
+
+       * dak/make_overrides.py (main): ditto
+
+       * dak/process_new.py (determine_new): ditto
+
+       * daklib/queue.py (Upload.in_override_p),
+       (Upload.check_override): ditto
+
+       * daklib/utils.py (extract_component_from_section):,
+       (poolify): ditto
+
+       * dak/import_archive.py (update_section): ditto
+
+       * dak/symlink_dists.py (fix_component_section): ditto
+
+       * scripts/debian/mkmaintainers: ditto
+
+       * scripts/debian/update-mirrorlists (masterlist): ditto
+
+       * config/debian-non-US/*: Remove subdir
+
+       * scripts/debian/update-readmenonus: Removed.
+
+
+2007-12-30  Joerg Jaspert  <joerg@debian.org>
+
+       * dak/dak.py (init): add show-new. This is based on a patch
+       submitted by Thomas Viehmann in Bug #408318, but large parts of
+       handling it are rewritten and show-new is done by me.
+
+       * dak/queue_report.py (table_row): Add link to generated html page
+       for NEW package.
+
+       * dak/show_new.py: new file, generates html overview for NEW
+       packages, similar to what we see with examine-package.
+
+       * config/debian/cron.hourly: Add show-new call
+
+       * config/debian/dak.conf: Add HTMLPath for Show-New
+
+       * dak/examine_package.py (print_copyright): ignore stderr when
+       finding copyright file.
+       (main): add html option
+       (html_escape): new function
+       (escape_if_needed): ditto
+       (headline): ditto
+       (colour_output): ditto
+       (print_escaped_text): ditto
+       (print_formatted_text): ditto
+       - use those functions everywhere where we generate output, as they
+       easily know if we want html or not and just DTRT
+       (do_lintian): new function
+       (check_deb): use it
+       (output_deb_info): Use print_escaped_text, not print_formatted_text.
+       Also import daklib.queue, determine_new now lives there
+
+       Also add a variable to see if we want html output. Default is
+       disabled, show_new enables it for its use.
+       Most of html, besides header/footer are in examine_package instead
+       of show_new, as it makes it a whole lot easier to deal with it at
+       the point the info is generated.
+
+
+       * dak/process_new.py (determine_new): Moved out of here.
+       (check_valid): Moved out of here.
+       (get_type): Moved out of here.
+
+       * daklib/queue.py (determine_new): Moved here.
+       (check_valid): Moved here.
+       (get_type): Moved here.
+
+       * dak/init_db.py (do_section): Remove non-US code
+
+       * dak/make_overrides.py (main): ditto
+
+       * dak/process_new.py (determine_new): ditto
+
+       * daklib/queue.py (Upload.in_override_p),
+       (Upload.check_override): ditto
+
+       * daklib/utils.py (extract_component_from_section):,
+       (poolify): ditto
+
+       * dak/import_archive.py (update_section): ditto
+
+       * dak/symlink_dists.py (fix_component_section): ditto
+
+       * scripts/debian/mkmaintainers: ditto
+
+       * scripts/debian/update-mirrorlists (masterlist): ditto
+
+       * config/debian-non-US/*: Remove subdir
+
+       * scripts/debian/update-readmenonus: Removed.
+
+
+2007-12-28  Anthony Towns  <ajt@debian.org>
+
+       * daklib/utils.py (check_signature): add NOTATION_DATA and
+       NOTATION_NAME to known keywords.
+
+       * daklib/queue.py (Upload.check_source_against_db):
+
+       * dak/make_suite_file_list.py: add -f/--force option.
+
+       * dak/generate_releases.py: add -a/--apt-conf=FILE and
+       -f/--force-touch options.  Pull version info from the database.
+       Make suite description optional.
+
+       * config/debian/dak.conf: update
+       Reject-Proposed-Updates::MoreInfoURL.  Comment out
+       Suite::Stable::Version and ::Description.
+
+       * config/debian/apt.conf: Add hurd-i386 to unstable
+       debian-installer stanza.
+
+2007-12-28  Joerg Jaspert  <joerg@debian.org>
+
+       * KEYEXPIRED is actually a known keyword. We do check it earlier
+       on and reject in case the sig is bad (or unknown)
+
+2007-12-28  Anthony Towns  <ajt@debian.org>
+
+       * daklib/utils.py (check_signature): add NOTATION_DATA and
+       NOTATION_NAME to known keywords.
+
+       * daklib/queue.py (Upload.check_source_against_db):
+
+       * dak/make_suite_file_list.py: add -f/--force option.
+
+       * dak/generate_releases.py: add -a/--apt-conf=FILE and
+       -f/--force-touch options.  Pull version info from the database.
+       Make suite description optional.
+
+       * config/debian/dak.conf: update
+       Reject-Proposed-Updates::MoreInfoURL.  Comment out
+       Suite::Stable::Version and ::Description.
+
+       * config/debian/apt.conf: Add hurd-i386 to unstable
+       debian-installer stanza.
+
+2007-12-28  Joerg Jaspert  <joerg@debian.org>
+
+       * KEYEXPIRED is actually a known keyword. We do check it earlier
+       on and reject in case the sig is bad (or unknown)
+
+2007-12-24  Joerg Jaspert  <joerg@debian.org>
+
+       * Also run lintian on the .dsc file to check the source itself.
+
+       * Fix the direct usage of ar | tar etc to get the copyright file
+       and use dpkg-deb, which is made for this and makes us able to
+       process data.tar.bz2 (or whatever format it will be in the
+       future).
+
+2007-12-24  Joerg Jaspert  <joerg@debian.org>
+
+       * Also run lintian on the .dsc file to check the source itself.
+
+       * Fix the direct usage of ar | tar etc to get the copyright file
+       and use dpkg-deb, which is made for this and makes us able to
+       process data.tar.bz2 (or whatever format it will be in the
+       future).
+
+2007-12-21  Joerg Jaspert  <joerg@debian.org>
+
+       * Remove the (now useless) check for a pre-depends on dpkg for
+         binaries that contain bzip2 compressed data tarballs.
+
+2007-12-21  Joerg Jaspert  <joerg@debian.org>
+
+       * Remove the (now useless) check for a pre-depends on dpkg for
+         binaries that contain bzip2 compressed data tarballs.
+
+2007-08-28  Anthony Towns  <ajt@debian.org>
+
+       * process_unchecked.py: Add support for automatic BYHAND
+       processing.
+       * config/debian/dak.conf, scripts/debian/byhand-tag: Automatic
+       processing of tag-overrides.
+       * examine_package.py: Summarise duplicate copyright file entries
+       (same md5sum) with a reference to the previous instance, rather
+       than repeating them.
+       * process_new.py: When rejecting from the p-u-new or o-p-u-new
+       holding queues, don't worry if dak has its own reasons for
+       rejecting the package as well as the SRMs.
+
+2007-08-28  Anthony Towns  <ajt@debian.org>
+
+       * process_unchecked.py: Add support for automatic BYHAND
+       processing.
+       * config/debian/dak.conf, scripts/debian/byhand-tag: Automatic
+       processing of tag-overrides.
+       * examine_package.py: Summarise duplicate copyright file entries
+       (same md5sum) with a reference to the previous instance, rather
+       than repeating them.
+       * process_new.py: When rejecting from the p-u-new or o-p-u-new
+       holding queues, don't worry if dak has its own reasons for
+       rejecting the package as well as the SRMs.
+
+2007-06-19  Anthony Towns  <ajt@debian.org>
+
+       * Add nm.debian.org pseudopackage
+
+2007-06-19  Anthony Towns  <ajt@debian.org>
+
+       * Add nm.debian.org pseudopackage
+
+2007-06-18  Anthony Towns  <ajt@debian.org>
+
+       * daklib/logging.py: Set umask to not exclude group-writability
+       so we don't get reminded at the start of each month. Thanks to
+       Random J.
+       * dak/override.py: More changes from Herr von Wifflepuck: warn
+       if section of source is different to binary section; restore
+       functionality on source-only overrides; croak if trying to set
+       priority of a source override; never set priority of source
+       overrides; correct typo in logging (s/priority/section/ at
+       one place)
+
+       * config/debian/apt.conf.oldstable: Added for oldstable point releases.
+       * config/debian/cron.daily: auotmatically accept/reject
+       oldstable-proposed-updates based on COMMENTS directory
+
+2007-06-18  Anthony Towns  <ajt@debian.org>
+
+       * config/debian/apt.conf, config/debian/apt.conf.stable,
+       config/debian/dak.conf: update for 4.0r0 (etch), and 3.1r6
+       (sarge), support for oldstable-proposed-updates, dropping m68k
+       from etch, creating etch-m68k suite, creating lenny.
+
+       * config/debian/vars: update for lenny
+
+       * config/debian/dak.conf: typo fix for Dinstall::GPGKeyring,
+       drop upload limitations, add release postgres user
+
+       * dak/process_new.py: support for automatically accepting and rejecting
+       packages from proposed-updates holding queues via COMMENTS directory
+       * cron.daily: automatically process COMMENTS-based approvals
+       and rejections for proposed-updates holding queues
+
+       * dak/process_unchecked.py: add support for oldproposedupdates
+       holding queue
+
+       * dak/control_suite.py: allow control-suite to work with etch-m68k
+
+       * dak/generate_releases.py: unlink old Release files before updating
+       them if nlinks > 1 (ie, if two files used to be the same, maybe they
+       shouldn't be when generate-releases is run)
+
+       * dak/generate_releases.py: add a couple of commented lines to make
+       it easier to deal with point releases
+
+       * dak/make_overrides.py: generate overrides for !contrib udebs
+
+       * docs/README.stable-point-release: update docs for doing a
+       point release
+
+2007-06-18  Anthony Towns  <ajt@debian.org>
+
+       * daklib/logging.py: Set umask to not exclude group-writability
+       so we don't get reminded at the start of each month. Thanks to
+       Random J.
+       * dak/override.py: More changes from Herr von Wifflepuck: warn
+       if section of source is different to binary section; restore
+       functionality on source-only overrides; croak if trying to set
+       priority of a source override; never set priority of source
+       overrides; correct typo in logging (s/priority/section/ at
+       one place)
+
+       * config/debian/apt.conf.oldstable: Added for oldstable point releases.
+       * config/debian/cron.daily: auotmatically accept/reject
+       oldstable-proposed-updates based on COMMENTS directory
+
+2007-06-18  Anthony Towns  <ajt@debian.org>
+
+       * config/debian/apt.conf, config/debian/apt.conf.stable,
+       config/debian/dak.conf: update for 4.0r0 (etch), and 3.1r6
+       (sarge), support for oldstable-proposed-updates, dropping m68k
+       from etch, creating etch-m68k suite, creating lenny.
+
+       * config/debian/vars: update for lenny
+
+       * config/debian/dak.conf: typo fix for Dinstall::GPGKeyring,
+       drop upload limitations, add release postgres user
+
+       * dak/process_new.py: support for automatically accepting and rejecting
+       packages from proposed-updates holding queues via COMMENTS directory
+       * cron.daily: automatically process COMMENTS-based approvals
+       and rejections for proposed-updates holding queues
+
+       * dak/process_unchecked.py: add support for oldproposedupdates
+       holding queue
+
+       * dak/control_suite.py: allow control-suite to work with etch-m68k
+
+       * dak/generate_releases.py: unlink old Release files before updating
+       them if nlinks > 1 (ie, if two files used to be the same, maybe they
+       shouldn't be when generate-releases is run)
+
+       * dak/generate_releases.py: add a couple of commented lines to make
+       it easier to deal with point releases
+
+       * dak/make_overrides.py: generate overrides for !contrib udebs
+
+       * docs/README.stable-point-release: update docs for doing a
+       point release
+
+2007-03-05  Anthony Towns  <ajt@debian.org>
+
+       * config/debian/dak.conf: update for 3.1r5.
+       * scripts/debian/ssh-move: add ssh-move script from debbugs
+       * config/debian/cron.unchecked: push version info to debbugs using
+       ssh-move.
+
+2007-03-05  Anthony Towns  <ajt@debian.org>
+
+       * config/debian/dak.conf: update for 3.1r5.
+       * scripts/debian/ssh-move: add ssh-move script from debbugs
+       * config/debian/cron.unchecked: push version info to debbugs using
+       ssh-move.
+
+2007-02-14  James Troup  <troup@ries.debian.org>
+
+       * docs/README.config: remove Dinstall::GroupOverrideFilename.
+       * config/debian/dak.conf: likewise.
+       * config/debian-non-US/dak.conf: likewise.
+       * config/debian-security/dak.conf: likewise.
+
+       * daklib/queue.py (Upload.close_bugs): no longer handle NMUs or
+       experimental differently, just close the bugs and let version
+       tracking sort it out.
+        (nmu_p): remove entire class - now unused.
+        (Upload.__init__): don't use nmu_p.
+
+2007-02-14  James Troup  <troup@ries.debian.org>
+
+       * docs/README.config: remove Dinstall::GroupOverrideFilename.
+       * config/debian/dak.conf: likewise.
+       * config/debian-non-US/dak.conf: likewise.
+       * config/debian-security/dak.conf: likewise.
+
+       * daklib/queue.py (Upload.close_bugs): no longer handle NMUs or
+       experimental differently, just close the bugs and let version
+       tracking sort it out.
+        (nmu_p): remove entire class - now unused.
+        (Upload.__init__): don't use nmu_p.
+
+2007-02-08  Anthony Towns  <ajt@debian.org>
+
+       * config/debian/dak.conf: update for 3.1r4.  Use new 'etch'
+       signing key.  Drop maximum index diffs down to 14.
+
+       * config/debian/apt.conf: add udeb support for non-free (testing,
+       unstable) and experimental.
+       * config/debian/dak.conf: likewise.
+
+       * dak/generate_releases.py (main): handle udebs in any component.
+
+       * daklib/queue.py (Upload.build_summaries): handle files without a
+       'type' gracefully.
+
+       * dak/generate_releases.py (print_sha256_files): new function.
+       (main): use it.
+
+       * dak/process_accepted.py (stable_install): fix name of template
+       mail.
+
+       * dak/process_unchecked.py (is_stableupdate): fix invocation of
+       database.get_suite_id().
+
+       * templates/process-new.bxa_notification: Update on request
+       of/after discussion with BIS staff.
+
+       * scripts/debian/mkfilesindices: also handle proposed-updates.
+
+2007-02-08  Ryan Murray  <rmurray@debian.org>
+
+       * config/debian/cron.monthly: use $ftpgroup instead of hardcoding
+       group name for chgrp of mail archives.
+
+       * daklib/queue.py (Upload.check_dsc_against_db): handle multiple
+       orig.tar.gz's by picking the first one by file id.
+
+       * dak/override.py (main): limit to binary overrides only for now.
+       (usage): update to match.
+
+       * config/debian/cron.daily: track when we have the accepted lock
+       and clean it up on exit if we have it.  Take/check the
+       cron.unchecked lock just before traping to cleanup on exit.
+       Remove potato override handling.  Remove any dangling symlinks in
+       /srv/incoming.d.o/buildd.  Clean up apt-ftparchive's databases.
+
+       * config/debian/apt.conf: change default compression scheme for
+       both Sources and Packages to gzip and bzip2 rather than
+       uncompressed and gzip (Packages) and gzip (Sources).  Use old
+       defaults for proposed-updates.
+
+       * dak/control_overrides.py (main): refuse to operate on
+       untouchable suites.
+
+       * config/debian/pseudo-packages.maintainers: drop install,
+       installation, boot-floppy, slink-cd, potato-cd and
+       nonus.debian.org.  Update base.
+       * config/debian/pseudo-packages.description: likewise.
+
+       * daklib/utils.py (re_srchasver): new regex.
+       (parse_changes): use regex to split 'Source (Version)' style
+       Source fields into 'source' and 'source-version'.
+
+       * config/debian/cron.daily: use $base instead of hardcoding path
+       name.
+
+       * scripts/debian/mkfilesindices: source 'vars' file and use it's
+       variables instead of hardcoding path names.
+
+       * config/debian/apt.conf: switch from /org to /srv.
+       * config/debian/apt.conf.buildd: likewise.
+       * config/debian/apt.conf.stable: likewise.
+       * config/debian/cron.daily: likewise.
+       * config/debian/cron.hourly: likewise.
+       * config/debian/cron.monthly: likewise.
+       * config/debian/cron.unchecked: likewise.
+       * config/debian/cron.weekly: likewise.
+       * config/debian/dak.conf: likewise.
+       * config/debian/vars: likewise.
+       * scripts/debian/mkfilesindices: likewise.
+
+2007-02-08  James Troup  <james@nocrew.org>
+
+       * dak/process_unchecked.py (check_signed_by_key): new function to
+       ensure .changes files are signed by an authorized uploader.
+       (process_it): use it.
+
+       * config/debian/dak.conf (Binary-Upload-Restrictions): new stanza
+       to configure per suite/component/architecture binary upload
+       restrictions.
+
+2007-02-08  Anthony Towns  <ajt@debian.org>
+
+       * config/debian/dak.conf: update for 3.1r4.  Use new 'etch'
+       signing key.  Drop maximum index diffs down to 14.
+
+       * config/debian/apt.conf: add udeb support for non-free (testing,
+       unstable) and experimental.
+       * config/debian/dak.conf: likewise.
+
+       * dak/generate_releases.py (main): handle udebs in any component.
+
+       * daklib/queue.py (Upload.build_summaries): handle files without a
+       'type' gracefully.
+
+       * dak/generate_releases.py (print_sha256_files): new function.
+       (main): use it.
+
+       * dak/process_accepted.py (stable_install): fix name of template
+       mail.
+
+       * dak/process_unchecked.py (is_stableupdate): fix invocation of
+       database.get_suite_id().
+
+       * templates/process-new.bxa_notification: Update on request
+       of/after discussion with BIS staff.
+
+       * scripts/debian/mkfilesindices: also handle proposed-updates.
+
+2007-02-08  Ryan Murray  <rmurray@debian.org>
+
+       * config/debian/cron.monthly: use $ftpgroup instead of hardcoding
+       group name for chgrp of mail archives.
+
+       * daklib/queue.py (Upload.check_dsc_against_db): handle multiple
+       orig.tar.gz's by picking the first one by file id.
+
+       * dak/override.py (main): limit to binary overrides only for now.
+       (usage): update to match.
+
+       * config/debian/cron.daily: track when we have the accepted lock
+       and clean it up on exit if we have it.  Take/check the
+       cron.unchecked lock just before traping to cleanup on exit.
+       Remove potato override handling.  Remove any dangling symlinks in
+       /srv/incoming.d.o/buildd.  Clean up apt-ftparchive's databases.
+
+       * config/debian/apt.conf: change default compression scheme for
+       both Sources and Packages to gzip and bzip2 rather than
+       uncompressed and gzip (Packages) and gzip (Sources).  Use old
+       defaults for proposed-updates.
+
+       * dak/control_overrides.py (main): refuse to operate on
+       untouchable suites.
+
+       * config/debian/pseudo-packages.maintainers: drop install,
+       installation, boot-floppy, slink-cd, potato-cd and
+       nonus.debian.org.  Update base.
+       * config/debian/pseudo-packages.description: likewise.
+
+       * daklib/utils.py (re_srchasver): new regex.
+       (parse_changes): use regex to split 'Source (Version)' style
+       Source fields into 'source' and 'source-version'.
+
+       * config/debian/cron.daily: use $base instead of hardcoding path
+       name.
+
+       * scripts/debian/mkfilesindices: source 'vars' file and use it's
+       variables instead of hardcoding path names.
+
+       * config/debian/apt.conf: switch from /org to /srv.
+       * config/debian/apt.conf.buildd: likewise.
+       * config/debian/apt.conf.stable: likewise.
+       * config/debian/cron.daily: likewise.
+       * config/debian/cron.hourly: likewise.
+       * config/debian/cron.monthly: likewise.
+       * config/debian/cron.unchecked: likewise.
+       * config/debian/cron.weekly: likewise.
+       * config/debian/dak.conf: likewise.
+       * config/debian/vars: likewise.
+       * scripts/debian/mkfilesindices: likewise.
+
+2007-02-08  James Troup  <james@nocrew.org>
+
+       * dak/process_unchecked.py (check_signed_by_key): new function to
+       ensure .changes files are signed by an authorized uploader.
+       (process_it): use it.
+
+       * config/debian/dak.conf (Binary-Upload-Restrictions): new stanza
+       to configure per suite/component/architecture binary upload
+       restrictions.
+
+2006-10-09  James Troup  <james.troup@canonical.com>
+
+       * dak/process_unchecked.py (check_timestamps): change match to
+       search as recent versions of python-apt prefix the string with 'E: '.
+
+2006-10-09  James Troup  <james.troup@canonical.com>
+
+       * dak/process_unchecked.py (check_timestamps): change match to
+       search as recent versions of python-apt prefix the string with 'E: '.
+
+2006-06-26  Ryan Murray  <rmurray@debian.org>
+
+       * dak/process_unchecked.py (check_files): strip optional source version
+       from Source: field in changes file, and ensure what is left is a valid
+       package name.
+
+2006-06-26  Ryan Murray  <rmurray@debian.org>
+
+       * dak/process_unchecked.py (check_files): strip optional source version
+       from Source: field in changes file, and ensure what is left is a valid
+       package name.
+
+2006-06-23  Ryan Murray  <rmurray@debian.org>
+
+       * dak/process_unchecked.py (check_files): also check ProposedUpdates
+       queue for source.
+
+2006-06-23  Ryan Murray  <rmurray@debian.org>
+
+       * dak/process_unchecked.py (check_files): also check ProposedUpdates
+       queue for source.
+
+2006-06-18  Ryan Murray  <rmurray@debian.org>
+
+       * dak/scripts/debian/update-ftpstats: look for dak named processes in
+       the log, too.
+
+       * dak/process_unchecked.py (check_files): only check embargoed and
+       unembargoed queues if the keys are set.
+
+       * dak/config/debian-security/apt.conf: set Packages::Compress to gzip
+       and bzip2 for etch.
+
+2006-06-18  Ryan Murray  <rmurray@debian.org>
+
+       * dak/scripts/debian/update-ftpstats: look for dak named processes in
+       the log, too.
+
+       * dak/process_unchecked.py (check_files): only check embargoed and
+       unembargoed queues if the keys are set.
+
+       * dak/config/debian-security/apt.conf: set Packages::Compress to gzip
+       and bzip2 for etch.
+
+2006-06-16  James Troup  <james@nocrew.org>
+
+       * dak/dak.py (init): add new-security-install.
+
+       * dak/new_security_install.py: cleanups and adapt for new naming
+       scheme and other changes.
+
+2006-06-16  Anthony Towns  <ajt@debian.org>
+
+       * dak/new_security_install.py: initial version imported from
+       klecker.
+
+2006-06-16  James Troup  <james@nocrew.org>
+
+       [Merged changes from klecker - original author unknown.]
+
+       * daklib/queue.py (Upload.dump_vars): also save changes["adv id"].
+
+       * dak/security_install.py (do_upload): fix check for oldstable and
+       upload file list handling.
+
+       * dak/process_unchecked.py (check_files): update "check for
+       source" code to also look in Embargoed and Unembargoed queues.
+       (is_unembargo): fix handling of Dir::Queue::Disembargo.
+
+       * dak/decode_dot_dak.py (main): add support for changes["adv id"].
+
+       * config/debian-security/vars (disembargo): add.
+
+       * config/debian-security/dak.conf (Dinstall::SigningKeyIds):
+       update.
+       (Process-Unchecked::AcceptedLockFile): add.
+       (Suite::Testing): clean up Version and Description.
+       (SuiteMappings): add silent map from etch-secure to testing.
+
+       * config/debian-security/cron.unchecked: add support for
+       disembargoed queues.
+
+       * config/debian-security/apt.conf.buildd: add bzip2ed Packages
+       files too.
+
+       * config/debian-security/apt.conf: add amd64 to architectures for
+       testing-security.
+
+2006-06-11  James Troup  <troup@spohr.debian.org>
+
+       * config/debian/cron.daily: invoke process-accepted not
+       process-unchecked.
+
+       * config/debian/vars (scriptsdir): new variable.
+       * config/debian/cron.daily: use it.
+
+       * scripts/debian/mkmaintainers: put Maintainers_Versions-non-US in
+       $base/misc/ instead of versioned $masterdir.  Correct 'dak
+       make-maintainers' invocation to look in $configdir for
+       pseudo-packages.maintainers.
+
+       * daklib/queue.py (Upload.do_reject): use correct name for
+       rejection template.
+
+2006-06-11  James Troup  <james@nocrew.org>
+
+       * dak/override.py (main): temporarily add content of old X-Katie
+       header back as the PTS apparently filters on it.
+       * dak/process_accepted.py (main): likewise.
+       * dak/process_new.py (main): likewise.
+       * dak/process_unchecked.py (main): likewise.
+       * dak/reject_proposed_updates.py (main): likewise.
+       * dak/rm.py (main): likewise.
+       * daklib/queue.py (Upload.do_reject): likewise.
+
+       * config/debian/cron.unchecked: set -u to error out on undefined
+       variables.  Preset LOCKDAILY to "" accordingly.
+       * config/debian/cron.hourly: likewise.
+       * config/debian/cron.monthly: likewise.
+       * config/debian/cron.weekly: likewise.
+
+       * config/debian/vars (configdir): add new variable pointing to
+       this directory.
+
+       * config/debian/cron.daily: use $configdir inplace of $masterdir
+       when that's what we mean, and don't cd into $masterdir just to run
+       dak scripts as we don't need to do that anymore.
+       * config/debian/cron.hourly: likewise.
+       * config/debian/cron.unchecked: likewise.
+       * config/debian/cron.weekly: likewise.
+
+       * config/debian/dak.conf
+       (Import-Users-From-Passwd::KnownPostgres): temporarily add 'katie'
+       user back to list of known users as it's non-trivial to entirely
+       change the owner of a database with postgresql 7.4.
+
+       * daklib/queue.py (Upload.source_exists): use string object
+       methods rather than string module.
+       (Upload.get_anyversion): likewise.
+
+       * daklib/utils.py (validate_changes_file_arg): update filename
+       slicing to cope with new .dak filenames.
+
+       * dak/ls.py (main): add back 'heidi' as a valid argument for
+       -f/--format as people are using it in scripts and breaking that
+       without warning seems rude.
+
+2006-05-21  James Troup  <james@nocrew.org>
+
+       * dak/rm.py (main): use string .isdigit() rather than
+       utils.str_isnum().
+       * dak/process_new.py (edit_overrides): likewise.
+
+       * daklib/utils.py (str_isnum): removed accordingly.  Also drop
+       string import.
+
+2006-05-21  James Troup  <james@nocrew.org>
+
+       * dak/check_archive.py (check_indices_files_exist): use list
+       comprehension instead of map().  No longer need to import
+       deprecated string module as a side-effect.
+       * dak/check_overrides.py (process): likewise.
+       (main): likewise.
+       * dak/cruft_report.py (do_obsolete_source): likewise.
+       (main): likewise.
+       * dak/ls.py (main): likewise.
+       * dak/make_suite_file_list.py (write_filelists): likewise.
+       * dak/process_accepted.py (stable_install): likewise.
+       * dak/rm.py (main): likewise.
+       * dak/stats.py (number_of_packages): likewise.
+       * daklib/logging.py (Logger.log): likewise.
+       * daklib/queue.py (Upload.source_exists): likewise.
+       (Upload.cross_suite_version_check): likewise.
+       * daklib/utils.py (parse_args): likewise.
+
+2006-05-21  James Troup  <james@nocrew.org>
+
+       * daklib/utils.py (process_gpgv_output): new function, split out
+       of check_signature().
+       (check_signature): adapt accordingly.
+       (retrieve_key): new function that will try to retrieve the key
+       that signed a given file from a keyserver.
+       (check_signature): add 'autofetch' argument that if not set
+       defaults to the value of Dinstall::KeyAutoFetch (if that exists).
+       If 'autofetch' is true, invoke retrieve_key().
+
+       * docs/README.config: document Dinstall::KeyAutoFetch and
+       Dinstall:KeyServer.
+
+2006-05-20  James Troup  <james@nocrew.org>
+
+       * dak/find_null_maintainers.py (main):
+       s/createtimestamp/createTimestamp/ to make things work with modern
+       slapd.
+
+       * config/debian/dak.conf: Update StableRejector, MoreInfoURL,
+       Stable::Version and Stable::Description for 3.1r2.
+
+       * config/debian-non-US/dak.conf: sync with klecker - update
+       version number of 3.0 and MoreInfoURL.
+
+       * docs/README.stable-point-release: Add notes about updating
+       dak.conf and Reject-Proposed-Updates section in particular.
+       s/woody/stable/.  Also need to update README.html.
+
+       * scripts/debian/mklslar: drop support for uncompressed ls-lR file.
+
+       * config/debian/apt.conf: Add udeb tree for proposed-updates.
+
+2006-05-20  Ryan Murray  <rmurray@debian.org>
+
+       * scripts/debian/update-ftpstats: new script to update daily
+       architecture size graph data.
+
+       * config/debian/cron.buildd: sync with spohr - due to ftp-master /
+       buildd split, simply ssh to buildd.d.o and call 'trigger.often'
+       there.
+
+       * config/debian/cron.daily: ssh to buildd and run 'trigger.daily'
+       before we finish.  Don't push to merkel.
+
+       * dak/process_unchecked.py (check_changes): Also look in
+       ProposedUpdates queue dir.
+       (check_files): likewise.
+
+2006-05-20  Anthony Towns  <ajt@debian.org>
+
+       * scripts/debian/mkfilesindices: new script to generate file
+       indices used for partial mirroring.
+
+       * config/debian/cron.daily: add progress timestamps.
+
+       * config/debian/dak.conf: update Dinstall::SigningKeyIds. Add
+       amd64 to testing, unstable and experimental.  Drop sh from
+       experimental.  Move Experimental to dists/experimental.  Add
+       Dir::ProposedUpdates.  Add Architectures::amd64.
+
+       * config/debian/apt.conf: add amd64 to testing, unstable and
+       experimental.  Drop uncompressed Packages files from testing. Drop
+       sh from experimental.  Move experimental to dists/experimental/.
+
+       * config/debian/vars (archs): add amd64.
+
+       * dak/process_unchecked.py (action): Add support for
+       proposed-updates approval queue.
+       (is_stableupdate): new function - checks if the upload is targeted
+       at proposed-updates.
+       (do_stableupdate): likewise - moves upload to proposed-updates
+       holding area.
+
+       * dak/process_new.py (do_new): warn if original or new target
+       suite are invalid.
+
+       * dak/generate_releases.py (print_md5sha_files): less whitespace
+       between the hash and size.
+
+       * dak/generate_index_diffs.py (genchanges): don't say we're not
+       doing anything.  Better formatting of other messages.
+       (main): don't skip experimental and remove some 'doing stuff'
+       prints.
+
+2006-05-18  James Troup  <james@nocrew.org>
+
+       * dak/clean_suites.py (clean_binaries): remove debug print of SQL
+       query.
+
+       * dak/init_dirs.py: pylint cleanups - long lines, unused globals,
+       docstrings, untabify, don't shadow builtins, lowercase non-global
+       variable names, spaces after commas.  Also bail if given any
+       arguments since we don't accept any.
+       * dak/init_db.py: likewise.  Also split large main() into
+       subfunctions and rename get() to sql_get().
+
+       * dak/init_db.py (main): check returned value from
+       database.get_archive_id().
+
+       * dak/dak.py: renamed from shell.py.  Update to support new source
+       layout.  Created init() and usage() functions.  Various
+       pylint-inspired cleanups.  Use daklib utils.warn() and
+       utils.fubar().  Change 'functionality' variable to only have
+       (command, description) and always invoke main() when running the
+       module.  Also support -h.
+
+2006-05-17  James Troup  <james@nocrew.org>
+
+       * dak/check_archive.py: remove $Id$ and $Revision$ strings.  Update
+       imports of and calls to daklib modules.  Change script name
+       everywhere, i.e. in output, mails, comments and configuration tree
+       + filenames.  Also update references to other renamed scripts,
+       classes and templates.  Use '.dak' instead of '.katie' for the
+       queue info storage files.
+       (Renamed from tea)
+       * dak/check_overrides.py: likewise.  (Renamed from cindy)
+       * dak/check_proposed_updates.py: likewise.  (Renamed from jeri)
+       * dak/clean_proposed_updates.py: likewise.  (Renamed from halle)
+       * dak/clean_queues.py: likewise.  (Renamed from shania)
+       * dak/clean_suites.py: likewise.  (Renamed from rhona)
+       * dak/compare_suites.py: likewise.  (Renamed from andrea)
+       * dak/control_overrides.py: likewise.  (Renamed from natalie)
+       * dak/control_suite.py: likewise.  (Renamed from heidi)
+       * dak/cruft_report.py: likewise.  (Renamed from rene)
+       * dak/decode_dot_dak.py: likewise.  (Renamed from ashley)
+       * dak/find_null_maintainers.py: likewise.  (Renamed from rosamund)
+       * dak/generate_index_diffs.py: likewise.  (Renamed from tiffani)
+       * dak/generate_releases.py: likewise.  (Renamed from ziyi)
+       * dak/import_archive.py: likewise.  (Renamed from neve)
+       * dak/import_ldap_fingerprints.py: likewise. (Renamed from emilie)
+       * dak/import_users_from_passwd.py: likewise. (Renamed from julia)
+       * dak/init_db.py: likewise. (Renamed from alyson)
+       * dak/init_dirs.py: likewise. (Renamed from rose)
+       * dak/ls.py: likewise. (Renamed from madison)
+       * dak/make_maintainers.py: likewise.  (Renamed from charisma)
+       * dak/make_overrides.py: likewise.  (Renamed from denise)
+       * dak/make_suite_file_list.py: likewise.  (Renamed from jenna)
+       * dak/mirror_split.py: likewise.  (Renamed from billie)
+       * dak/override.py: likewise.  (Renamed from alicia)
+       * dak/poolize.py: likewise.  (Renamed from catherine)
+       * dak/process_accepted.py: likewise.  (Renamed from kelly)
+       * dak/process_new.py: likewise.  (Renamed from lisa)
+       * dak/process_unchecked.py: likewise.  (Renamed from jennifer)
+       * dak/queue_report.py: likewise.  (Renamed from helena)
+       * dak/reject_proposed_updates.py: likewise.  (Renamed from lauren)
+       * dak/rm.py: likewise.  (Renamed from melanie)
+       * dak/security_install.py: likewise.  (Renamed from amber)
+       * dak/split_done.py: likewise.  (Renamed from nina)
+       * dak/stats.py: likewise.  (Renamed from saffron)
+       * dak/symlink_dists.py: likewise.  (Renamed from saffron)
+       * daklib/database.py: likewise.  (Renamed from db_access)
+       * daklib/queue.py: likewise.  'Katie' class -> 'Upload'. (Renamed from katie)
+       * daklib/utils.py: likewise.
+
+       * dak/cruft_report.py: Use '[auto-cruft]' as the magic "this removal
+       doesn't need to notify anyone" string.
+       * dak/rm.py: likewise, look for '[auto-cruft]' as the magic string.
+
+       * dak/process_accepted.py (init): drop -V/--version argument.
+       * dak/process_new.py (init): likewise.
+       * dak/process_unchecked.py (init): likewise.
+       * dak/reject_proposed_updates.py (init): likewise
+
+       * dak/shell.py: Renamed from dak.  ".katie" -> ".dak"
+
+       * dak/stats.py: in usage() output change STAT to MODE.
+
+2006-05-15  James Troup  <james@nocrew.org>
+
+       * dak/queue_report.py: remove unused encodings imports.
+
+       * dak/mirror_split.py: drop unused pg, pwd, db_access and logging
+       imports.  Initalize 'Cnf' as a global.
+       (BillieDB._internal_recurse): fix 'util.' typo.
+
+       * dak/import_ldap_fingerprints.py (main): drop unused time import and
+       commented out time check for LDAP search.
+
+2005-12-16  Ryan Murray  <rmurray@debian.org>
+
+       * halle: add support for udebs
+       * kelly: stable_install: add support for binNMU versions
+
+2005-12-05  Anthony Towns  <aj@erisian.com.au>
+
+       * katie.py: Move accept() autobuilding support into separate function 
+       (queue_build), and generalise to build different queues
+
+       * db_access.py: Add get_or_set_queue_id instead of hardcoding accepted=0
+
+       * jennifer: Initial support for enabling embargo handling with the
+       Dinstall::SecurityQueueHandling option.
+       * jennifer: Shift common code into remove_from_unchecked and move_to_dir
+       functions.
+
+       * katie.conf-security: Include embargo options
+       * katie.conf-security: Add Lock dir
+       * init_pool.sql-security: Create disembargo table
+       * init_pool.sql-security: Add constraints for disembargo table
+
+2005-11-26  Anthony Towns  <aj@erisian.com.au>
+
+       * Merge of changes from klecker, by various people
+
+       * amber: special casing for not passing on amd64 and oldstable updates
+       * amber: security mirror triggering
+       * templates/amber.advisory: updated advisory structure
+       * apt.conf.buildd-security: update for sarge's release
+       * apt.conf-security: update for sarge's release
+       * cron.buildd-security: generalise suite support, update for sarge's release
+       * cron.daily-security: update for sarge's release, add udeb support
+       * vars-security: update for sarge's release
+       * katie.conf-security: update for sarge's release, add amd64 support,
+       update signing key
+
+       * docs/README.names, docs/README.quotes: include the additions
+
+2005-11-25  Anthony Towns  <aj@erisian.com.au>
+
+       * Changed accepted_autobuild to queue_build everywhere.
+       * Add a queue table.
+       * Add a "queue" field in the queue_build table (currently always 0)
+
+       * jennifer: Restructure to make it easier to support special
+       purpose queues between unchecked and accepted.
+
+2005-11-25  Anthony Towns  <aj@erisian.com.au>
+
+       * Finishing merge of changes from spohr, by various people still
+
+       * jennifer: If changed-by parsing fails, set variables to "" so REJECT
+       works
+       * jennifer: Re-enable .deb ar format checking
+       * katie.py: Convert to +bX binNMU special casing
+       * rhona: Add some debug output when deleting binaries
+       * cron.daily: Add emilie
+       * cron.unchecked: Add lock files
+
+2005-11-15  Anthony Towns  <aj@erisian.com.au>
+
+       * Merge of changes from spohr, by various people.
+
+       * tiffani: new script to do patches to Packages, Sources and Contents
+       files for quicker downloads.
+       * ziyi: update to authenticate tiffani generated files
+
+       * dak: new script to provide a single binary with less arbitrary names
+       for access to dak functionality.
+
+       * cindy: script implemented
+
+       * saffron: cope with suites that don't have a Priority specified
+       * heidi: use get_suite_id()
+       * denise: don't hardcode stable and unstable, or limit udebs to unstable
+       * denise: remove override munging for testing (now done by cindy)
+       * helena: expanded help, added new, sort and age options, and fancy headers
+       * jennifer: require description, add a reject for missing dsc file
+       * jennifer: change lock file
+       * kelly: propogation support
+       * lisa: honour accepted lock, use mtime not ctime, add override type_id
+       * madison: don't say "dep-retry"
+       * melanie: bug fix in output (missing %)
+       * natalie: cope with maintainer_override == None; add type_id for overrides
+       * nina: use mtime, not ctime
+
+       * katie.py: propogation bug fixes
+       * logging.py: add debugging support, use | as the logfile separator
+
+       * katie.conf: updated signing key (4F368D5D)
+       * katie.conf: changed lockfile to dinstall.lock
+       * katie.conf: added Lisa::AcceptedLockFile, Dir::Lock
+       * katie.conf: added tiffani, cindy support
+       * katie.conf: updated to match 3.0r6 release
+       * katie.conf: updated to match sarge's release
+
+       * apt.conf: update for sarge's release
+       * apt.conf.stable: update for sarge's release
+       * apt.conf: bump daily max Contents change to 25MB from 12MB
+
+       * cron.daily: add accepted lock and invoke cindy
+       * cron.daily: add daily.lock
+       * cron.daily: invoke tiffani
+       * cron.daily: rebuild accepted buildd stuff
+       * cron.daily: save rene-daily output on the web site
+       * cron.daily: disable billie
+       * cron.daily: add stats pr0n
+
+       * cron.hourly: invoke helena
+
+       * pseudo-packages.maintainers,.descriptions: miscellaneous updates
+       * vars: add lockdir, add etch to copyoverrides
+       * Makefile: add -Ipostgresql/server to CXXFLAGS
+
+       * docs/: added README.quotes
+       * docs/: added manpages for alicia, catherine, charisma, cindy, heidi,
+       julia, katie, kelly, lisa, madison, melanie, natalie, rhona.
+
+       * TODO: correct spelling of "conflicts"
+
+2005-05-28  James Troup  <james@nocrew.org>
+
+       * helena (process_changes_files): use MTIME rather than CTIME (the
+       C's not for 'creation', stupid).
+       * lisa (sort_changes): likewise.
+
+       * jennifer (check_distributions): use has_key rather than an 'in'
+       test which doesn't work with python2.1.  [Probably by AJ]
+
+2005-03-19  James Troup  <james@nocrew.org>
+
+       * rene (main): use Suite::<suite>::UdebComponents to determine
+       what components have udebs rather than assuming only 'main' does.
+
+2005-03-18  James Troup  <james@nocrew.org>
+
+       * utils.py (rfc2047_encode): use codecs.lookup() rather than
+       encodings.<encoding>.Codec().decode() as encodings.utf_8 no longer
+       has a Codec() module in python2.4.  Thanks to Andrew Bennetts
+       <andrew@ubuntu.com>.
+
+2005-03-06  Joerg Jaspert  <ganneff@debian.org>
+
+       * helena: add -n/--new HTML output option and improved sorting
+       options.
+
+2005-03-06  Ryan Murray  <rmurray@debian.org>
+
+       * shania(main): use Cnf::Dir::Reject instead of REJECT
+
+2005-02-08  James Troup  <james@nocrew.org>
+
+       * rene (main): add partial NBS support by checking that binary
+       packages are built by their real parent and not some random
+       stranger.
+       (do_partial_nbs): likewise.
+
+2005-01-18  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.build_summaries): avoid leaking file handle when
+       extracting package description.
+       (Katie.force_reject): remember and close each file descriptor we
+       use.
+       (Katie.do_reject): s/file/temp_fh/ to avoid pychecker warning.
+       s/reason_file/reason_fd/ because it's a file descriptor.
+       (Katie.check_dsc_against_db): avoid leaking file handle whenever
+       invoking apt_pkg.md5sum().
+
+       * jennifer (check_deb_ar): new function: sanity check the ar
+       contents of a .deb.
+       (check_files): use it.
+       (check_timestamps): check for data.tar.bz2 if data.tar.gz can't be
+       found.
+       (check_files): accept 'raw-installer' as an alias for 'byhand'.
+
+2005-01-14  Anthony Towns  <ajt@debian.org>
+
+       * kelly: when UNACCEPTing, don't double up the "Rejecting:"
+
+       * propup stuff (thanks to Andreas Barth)
+       * katie.conf: add stable MustBeOlderThan testing, add -security
+         propup
+       * jennifer: set distribution-version in .katie if propup may be needed
+       * katie.py: add propogation to cross_suite_version_check
+
+2004-11-27  James Troup  <james@nocrew.org>
+
+       * nina: new script to split monolithic queue/done into date-based
+       hierarchy.
+
+       * rene (usage): document -s/--suite.
+       (add_nbs): use .setdefault().
+       (do_anais): likewise.
+       (do_nbs): don't set a string to "" and then += it.
+       (do_obsolete_source): new function - looks for obsolete source
+       packages (i.e source packages whose binary packages are ALL a)
+       claimed by someone else and b) newer when built from the other
+       source package).
+       (main): support -s/--suite.  Add 'obsolete source' to both 'daily'
+       and 'full' check modes.  Check for obsolete source packages.
+       linux-wlan-ng has been fixed - remove hideous bodge.
+
+       * jennifer (check_distributions): support 'reject' suite map type.
+
+       * utils.py (validate_changes_file_arg): s/file/filename/.
+       s/fatal/require_changes/.  If require_changes is -1, ignore errors
+       and return the .changes filename regardless.
+       (re_no_epoch): s/\*/+/ as there must be a digit in an epoch.
+       (re_no_revision): don't escape '-', it's not a special character.
+       s/\*/+/ as there must be at least one non-dash character after the
+       dash in a revision.  Thanks to Christian Reis for noticing both of
+       these.
+
+       * ashley (main): pass require_changes=-1 to
+       utils.validate_changes_file_arg().
+
+       * pseudo-packages.maintainers (kernel): switch to 'Debian Kernel
+       Team <debian-kernel@lists.debian.org>'.
+
+       * katie.py (Katie.in_override_p): fix .startswith() usage.
+
+       * katie.conf (Dinstall::DefaultSuite): add as 'unstable'.
+       (Lauren::MoreInfoURL): update to 3.0r3.
+       (Suite::Stable::Version): likewise.
+       (Suite::Stable::Description): likewise.
+
+       * cron.daily: disable automatic task override generation.
+
+       * cindy (process): restrict "find all packages" queries by
+       component.  Respect Options["No-Action"].
+       (main): add -n/--no-action support.  Only run on unstable.  Rename
+       type to otype (pychecker).
+
+2004-11-27  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * katie.conf (Billie::BasicTrees): add all architectures.
+       (Billie::CombinationTrees): remove 'welovehp' and 'embedded', add
+       'everything'.
+
+       * cron.daily: Update a 'current' symlink when creating the
+       post-daily-cron-job database backup to aid mirroring to merkel.
+       Run billie.
+
+       * billie (BillieTarget.poolish_match): handle .udeb too.
+
+2004-10-13  Ryan Murray  <rmurray@debian.org>
+
+       * amber (do_upload): Sort changes files in "katie" order so that
+         source always arrives before binary-only rebuilds
+
+2004-10-05  James Troup  <james@nocrew.org>
+
+       * jennifer (check_dsc): correct reject message on invalid
+       Maintainer field.
+
+2004-09-20  James Troup  <james@nocrew.org>
+
+       * alicia: remove unused 'pwd' import.
+
+       * tea (check_override): underline suite name in output properly.
+
+       * rene (main): read a compressed Packages file.
+       * tea (validate_packages): likewise.
+
+       * katie.py (re_fdnic): add 'r' prefix.
+       (re_bin_only_nmu_of_mu): likewise.
+       (re_bin_only_nmu_of_nmu): likewise.
+
+       * madison (main): retrieve component information too and display
+       it if it's not 'main'.
+       * melanie (reverse_depends_check): likewise.
+
+       * utils.py (pp_dep): renamed...
+       (pp_deps): ... to this.
+       * jeri (check_dep): update calls to utils.pp_deps().
+       * melanie (reverse_depends_check): likewise.
+
+       * jennifer (check_changes): move initalization of email variables
+       from here...
+       (process_it): ...to here as we no longer always run
+       check_changes().  Don't bother to initialize
+       changes["architecture"].
+
+       * denise (list): renamed to...
+       (do_list): ...this to avoid name clash with builtin 'list'.
+       Similarly, s/file/output_file/, s/type/otype/.  Use .setdefault()
+       for dictionaries.
+       (main): Likewise for name clash avoidance and also
+       s/override_type/suffix/.  Adjust call to do_list().
+
+2004-09-01  Ryan Murray  <rmurray@debian.org>
+
+       * tea (check_files): check the pool/ directory instead of dists/
+
+2004-08-04  James Troup  <james@nocrew.org>
+
+       * jenna (cleanup): use .setdefault() for dictionaries.
+       (write_filelists): likewise.
+
+       (write_filelists): Use utils.split_args() not split() to split
+       command line arguments.
+       (stable_dislocation_p): likewise.
+
+       (write_filelists): Add support for mapping side of suite-based
+       "Arch: all mapping".
+       (do_da_do_da): ensure that if we're not doing all suites that we
+       process enough to be able correct map arch: all packages.
+
+       * utils.py (cant_open_exc): correct exception string,
+       s/read/open/, s/.$//.
+
+       * templates/amber.advisory: update to match reality a little
+       better.
+
+       * melanie (reverse_depends_check): read Packages.gz rather than
+       Packages.
+
+       * jennifer (check_files): check for unknown component before
+       checking for NEWness.
+
+       * katie.py (Katie.in_override_p): use .startswith in favour of a
+       slice.
+
+       * docs/melanie.1.sgml: document -R/--rdep-check.
+
+2004-07-12  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * billie (main): Make the verbatim lists include all the README
+         elements.
+       * docs/README.names: Add billie in (correcting oversight)
+
+2004-07-01  James Troup  <james@nocrew.org>
+
+       * emilie (main): handle woody's case-sensitive python-ldap,
+       s/keyfingerprint/keyFingerPrint/.
+
+2004-06-25  James Troup  <james@nocrew.org>
+
+       * debian/control (Depends): add dpkg-dev since jennifer uses
+       dpkg-source.
+
+2004-06-24  James Troup  <james@nocrew.org>
+
+       * melanie (main): s/file/temp_file/ and close file handle before
+       removing the temporary file.
+       (main): don't warn about needing a --carbon-copy if in no-action
+       mode.
+
+       * rene (do_nbs): pcmcia-cs has been fixed - remove hideous bodge.
+       (main): likewise.
+
+       * test/006/test.py (main): check bracketed email-only form.
+
+       * utils.py (fix_maintainer): if the Maintainer string is bracketed
+       email-only, strip the brackets so we don't end up with
+       <<james@nocrew.org>>.
+
+2004-06-20  James Troup  <james@nocrew.org>
+
+       * jennifer (process_it): only run check_changes() if
+       check_signature() returns something.  (Likewise)
+
+       * utils.py (changes_compare): if there's no changes["version"] use
+       "0" rather than None.  (Avoids a crash on unsigned changes file.)
+
+2004-06-17  Martin Michlmayr  <tbm@cyrius.com>
+
+       * jeri (pp_dep): moved from here to ...
+       * utils.py (pp_dep): here.
+
+       * melanie (main): add reverse dependency checking.
+
+2004-06-17  James Troup  <james@nocrew.org>
+
+       * jennifer (check_dsc): s/dsc_whitespace_rules/signing_rules/.
+       * tea (check_dscs): likewise.
+
+       * utils.py (parse_changes): s/dsc_whitespace_rules/signing_rules/,
+       change from boolean to a variable with 3 possible values, 0 and 1
+       as before, -1 means don't require a signature.  Makes
+       parse_changes() useful for parsing arbitary RFC822-style files,
+       e.g. 'Release' files.
+       (check_signature): add support for detached signatures by passing
+       the files the signature is for as an optional third argument.
+       s/filename/sig_filename/g.  Add a fourth optional argument to
+       choose the keyring(s) to use.  Don't os.path.basename() the
+       sig_filename before checking it for taint.
+       (re_taint_free): allow '/'.
+
+2004-06-11  James Troup  <james@nocrew.org>
+
+       * tea (check_files): make override.unreadable optional.
+       (validate_sources): close the Sources file handle.
+
+       * docs/README.first: clarify that 'alyson' and running
+       add_constraints.sql by hand is something you only want to do if
+       you're not running 'neve'.
+
+       * docs/README.config (Location::$LOCATION::Suites): document.
+
+       * db_access.py (do_query): also print out the result of the query.
+
+2004-06-10  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.cross_suite_version_check): post-woody versions
+       of python-apt's apt_pkg.VersionCompare() function apparently
+       returns variable integers for less than or greater than results -
+       update our result checking to match.
+       * jenna (resolve_arch_all_vs_any): likewise.
+       * charisma (main): likewise.
+
+2004-06-09  James Troup  <james@nocrew.org>
+
+       * jennifer (process_it): s/changes_valid/valid_changes_p/.  Add
+       valid_dsc_p and don't run check_source() if check_dsc() failed.
+       (check_dsc): on fatal failures return 0 so check_source() isn't
+       run (since it makes fatal assumptions about the presence of
+       mandatory .dsc fields).
+       Remove unused and obsolete re_bad_diff and re_is_changes regexps.
+
+2004-05-07  James Troup  <james@nocrew.org>
+
+       * katie.conf (Rhona::OverrideFilename): unused and obsolete, remove.
+       * katie.conf-non-US (Rhona::OverrideFilename): likewise.
+
+       * katie.conf (Dir::Override): remove duplicate definition.
+
+       * neve (get_or_set_files_id): add an always-NULL last_used column
+       to output.
+
+2004-04-27  James Troup  <james@nocrew.org>
+
+       * apt.conf-security (tree "dists/stable/updates"): add
+       ExtraOverride - noticed by Joey Hess (#246050).
+       (tree "dists/testing/updates"): likewise.
+
+2004-04-20  James Troup  <james@nocrew.org>
+
+       * jennifer (check_files): check for existing .changes or .katie
+       files of the same name in the Suite::<suite>::Copy{Changes,Katie}
+       directories.
+
+2004-04-19  James Troup  <james@nocrew.org>
+
+       * jennifer (check_source): handle failure to remove the temporary
+        directory (used for source tree extraction) better, specifically:
+        if we fail with -EACCES, chmod -R u+rwx the temporary directory
+        and try again and if that works, REJECT the package.
+
+2004-04-17  James Troup  <james@nocrew.org>
+
+       * docs/madison.1.sgml: document -b/--binary-type,
+       -g/--greaterorequal and -G/--greaterthan.
+
+       * madison (usage): -b/--binary-type only takes a single argument.
+       Document -g/--greaterorequal and -G/--greaterthan.
+       (main): add support for -g/--greaterorequal and -G/--greaterthan.
+
+2004-04-12  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * billie: Cleaned up a load of comments, added /README.non-US to
+         the verbatim matches list.
+
+2004-04-07  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * utils.py (size_type): Make it use real binary megabytes and
+         kilobytes, instead of the marketing terms used before.
+
+2004-04-07  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.check_dsc_against_db): in the case we're
+       ignoring an identical-to-existing orig.tar.gz remember the path to
+       the existent version in pkg.orig_tar_gz.  Adjust query to grab
+       location.path too to be able to do so.
+
+2004-04-03  James Troup  <james@nocrew.org>
+
+       * debian/control (Depends): add python2.1-email | python (>= 2.2)
+       needed for new utils.rfc2047_encode() function.
+
+       * utils.py (re_parse_maintainer): allow whitespace inside the
+       email address.
+       (Error): new exception base class.
+       (ParseMaintError): new exception class.
+       (force_to_utf8): new function.
+       (rfc2047_encode): likewise.
+       (fix_maintainer): rework.  use force_to_utf8() to force name and
+       rfc822 return values to always use UTF-8.  use rfc2047_encode() to
+       return an rfc2047 value.  Validate the address to catch missing
+       email addresses and (some) broken ones.
+
+       * katie.py (nmu_p.is_an_nmu): adapt for new utils.fix_maintainer()
+       by adopting foo2047 return value.
+       (Katie.dump_vars): add changedby2047 and maintainer2047 as
+       mandatory changes fields.  Promote changes and maintainer822 to
+       mandatory fields.
+       (Katie.update_subst): default maintainer2047 rather than
+       maintainer822.  User foo2047 rather than foo822 when setting
+       __MAINTAINER_TO__ or __MAINTAINER_FROM__.
+
+       * jennifer (check_changes): set default changes["maintainer2047"]
+       and changes["changedby2047"] values rather than their 822
+       equivalents.  Makes changes["changes"] a mandatory field.  Adapt
+       to new utils.fix_maintainer() - reject on exception and adopt
+       foo2047 return value.
+       (check_dsc): if a mandatory field is missing don't do any further
+       checks and as a result reduce paranoia about dsc[var] existence.
+       Validate the maintainer field by calling new
+       utils.fix_maintainer().
+
+       * ashley (main): add changedby2047 and maintainer2047 to mandatory
+       changes fields.  Promote maintainer822 to a mandatory changes
+       field.  add "pool name" to files fields.
+
+       * test/006/test.py: new file - tests for new
+       utils.fix_maintainer().
+
+2004-04-01  James Troup  <james@nocrew.org>
+
+       * templates/lisa.prod (To): use __MAINTAINER_TO__ not __MAINTAINER__.
+
+       * jennifer (get_changelog_versions): create a symlink mirror of
+       the source files in the temporary directory.
+       (check_source): if check_dsc_against_db() couldn't find the
+       orig.tar.gz bail out.
+
+       * katie.py (Katie.check_dsc_against_db): if the orig.tar.gz is not
+       part of the upload store the path to it in pkg.orig_tar_gz and if
+       it can't be found set pkg.orig_tar_gz to -1.
+
+       Explicitly return the second value as None in the (usual) case
+       where we don't have to reprocess.  Remove obsolete diagnostic
+       logs.
+
+       * lisa (prod_maintainer): don't return anything, no one cares. (pychecker)
+
+       * utils.py (temp_filename): new helper function that wraps around
+       tempfile.mktemp().
+
+       * katie.py (Katie.do_reject): use it and don't import tempfile.
+       * lisa (prod_maintainer): likewise.
+       (edit_note): likewise.
+       (edit_new): likewise.
+       * lauren (reject): likewise.
+       * melanie (main): likewise.
+       * neve (do_sources): likewise.
+       * rene (main): likewise.
+       * tea (validate_sources): likewise.
+
+2004-03-31  James Troup  <james@nocrew.org>
+
+       * tea (validate_sources): remove unused 's' temporary variable.
+
+2004-03-15  James Troup  <james@nocrew.org>
+
+       * jennifer (check_dsc): check changes["architecture"] for
+       source before we do anything else.
+
+2004-03-21  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * billie: Added
+       * katie.conf (Billie): Added sample Billie stanza to katie.conf
+
+2004-03-12  James Troup  <james@nocrew.org>
+
+       * docs/README.config (Dir::Queue::BTSVersionTrack): document.
+
+       * katie.conf (Dir::Queue::BTSVersionTrack): define.
+
+       * katie.py (Katie.accept): add support for DebBugs Version
+       Tracking by writing out .versions (generated in jennifer's
+       get_changelog_versions()) and .debinfo (mapping of binary ->
+       source) files.
+
+       * ashley (main): add dsc["bts changelog"].
+
+       * katie.py (Katie.dump_vars): store dsc["bts changelog"] too.
+
+       * jennifer (check_diff): obsoleted by check_source(), removed.
+       (check_source): new function: create a temporary directory and
+       move into it and call get_changelog_versions().
+       (get_changelog_versions): new function: extract the source package
+       and optionally parse debian/changelog to obtain the version
+       history for the BTS.
+       (process_it): call check_source() rather than check_diff().
+
+2004-03-08  James Troup  <james@nocrew.org>
+
+       * lisa (edit_index): Fix logic swapo from 'use "if varfoo in
+       listbar" rather than "if listbar.count(varfoo)"' change on
+       2004-02-24.
+
+2004-03-05  James Troup  <james@nocrew.org>
+
+       * alicia (main): don't warn about not closing bugs - we don't
+       manage overrides through the BTS.
+
+2004-02-27  Martin Michlmayr  <tbm@cyrius.com>
+
+       * docs/README.config: lots of updates and corrections.
+       * docs/README.first: likewise.
+
+       * docs/README.config: drop unused Dir::Queue::Root.
+       * katie.conf-non-US: likewise.
+       * katie.conf: likewise.
+       * katie.conf-security: likewise.
+
+2004-02-27  James Troup  <james@nocrew.org>
+
+       * rose (process_tree): use 'if var in [ list ]' rather than long
+       'if var == foo or var == bar or var == baz'.  Suggested by Martin
+       Michlmayr.
+
+       * jennifer (check_files): reduce 'if var != None' to 'if var' as
+       suggested by Martin Michlmayr.
+       * catherine (poolize): likewise.
+       * charisma (main): likewise.
+       * halle (check_changes): likewise.
+       * heidi (main): likewise.
+       (process_file): likewise.
+       * kelly (install): likewise.
+       (stable_install): likewise.
+       * utils.py (fix_maintainer): likewise.
+
+       * apt.conf: add support for debian-installer in testing-proposed-updates.
+       * katie.conf (Suite::Testing-Proposed-Updates::UdebComponents):
+       add - set to main.
+
+       * mkmaintainers: add "-T15" option to wget of non-US packages file
+       so that we don't hang cron.daily if non-US is down.
+
+       * templates/lisa.prod (Subject): Prefix with "Comments regarding".
+
+       * templates/jennifer.bug-close: add Source and Source-Version
+       pseudo-headers that may be used for BTS Version Tracking someday
+       [ajt@].
+
+       * rene (do_nbs): special case linux-wlan-ng like we do for pcmcia.
+       (main): likewise.
+
+       * cron.unchecked: it's /org/ftp.debian.org not ftp-master.
+
+2004-02-25  James Troup  <james@nocrew.org>
+
+       * katie.conf (SuiteMappings): don't map testing-security to
+       proposed-updates.
+
+2004-02-24  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.__init__): remove unused 'values' field.
+
+       * utils.py (extract_component_from_section): use 's.find(c) != -1'
+       rather than 's.count(c) > 0'.
+
+       * katie.py (Katie.source_exists): use "if varfoo in listbar"
+       rather than "if listbar.count(varfoo)".
+       * halle (check_joey): likewise.
+       * jeri (check_joey): likewise.
+       * lisa (edit_index): likewise.
+       * jenna (stable_dislocation_p): likewise.
+
+       * jennifer (main): remove unused global 'nmu'.
+
+2004-02-03  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * pseudo-packages.maintainers (ftp.debian.org): Changed the maintainer
+         to be ftpmaster@ftp-master.debian.org to bring it into line with how
+         the dak tools close bugs.
+
+2004-02-02  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * katie.conf (Alicia): Added an Alicia section with email address
+       * templates/alicia.bug-close: Added
+       * docs/alicia.1.sgml: Added the docs for the -d/--done argument
+       * alicia (main): Added a -d/--done argument
+
+2004-02-02  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * templates/lisa.prod: Oops, missed a BITCH->PROD conversion
+
+2004-01-29  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * lisa (prod_maintainer): Added function to prod the maintainer without
+         accepting or rejecting the package
+       * templates/lisa.prod: Added this template for the prodding mail
+
+       * .cvsignore: Added neve-files which turns up in new installations
+
+2004-01-30  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * alicia (usage): Fixed usage message to offer section and priority
+         as seperately optional arguments.
+       * alicia (main): Added a % (arg) interpolation needed when only
+         one of section or priority is provided and it cannot be found.
+
+2004-01-29  Daniel Silverstone  <dsilvers@digital-scurf.org>
+
+       * alicia: Added
+       * docs/alicia.1.sgml: Added
+       * docs/Makefile: Added alicia to the list of manpages to build
+       * docs/README.names: Noted what alicia does
+       * docs/README.first: Noted where alicia is useful
+
+2004-01-21  James Troup  <james@nocrew.org>
+
+       * madison (main): add -b/--binary-type.
+       (usage): likewise.
+
+       * denise (main): generate debian-installer overrides for testing
+       too.
+       * apt.conf: add support for debian-installer in testing.
+       * katie.conf (Suite::Testing::UdebComponents): set to main.
+
+       * katie.conf (Dinstall::SigningKeyIds): 2004 key.
+       * katie.conf-non-US (Dinstall::SigningKeyIds): likewise.
+       * katie.conf-security (Dinstall::SigningKeyIds): likewise.
+
+       * utils.py (parse_changes): don't process data not inside the
+       signed data.  Thanks to Andrew Suffield <asuffield@debian.org> for
+       pointing this out.
+       * test/005/test.py (main): new test to test for above.
+
+2004-01-04  James Troup  <james@nocrew.org>
+
+       * jenna (write_filelists): correct typo, s/Components/Component/
+       for Options.
+
+2004-01-04  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: move update of overrides and Packages file...
+       * cron.unchecked: to here.
+       * katie.conf-non-US: (Dinstall::SingingKeyIds) update for 2003v2 key
+       * katie.conf-security: likewise
+
+2003-11-20  James Troup  <james@nocrew.org>
+
+       * jenna (main): don't use utils.try_with_debug(), it produces way
+       too much output.
+
+       * halle (check_changes): don't error out if a .changes refers to a
+       non-existent package, just warn and skip the file.
+
+       * docs/README.stable-point-release: mention halle and .changes
+       obsoleted by removal through melanie.  Update for 3.0r2.
+
+       * katie.conf (Suite::Stable::Version): bump to 3.0r2.
+       (Suite::Stable::Description): update for 3.0r2.
+       (Lauren::MoreInfoURL): likewise.
+       * katie.conf-non-US (Suite::Stable::Version): likewise.
+       (Suite::Stable::Description): likewise.
+       (Lauren::MoreInfoURL): likewise.
+
+       * apt.conf.stable (Default): don't define MaxContentsChange.
+       * apt.conf.stable-non-US (Default): likewise.
+
+       * lauren (reject): hack to work around partial replacement of an
+       upload, i.e. one or more binaries superseded by another source
+       package.
+
+2003-11-17  James Troup  <james@nocrew.org>
+
+       * pseudo-packages.maintainers: point installation-reports at
+       debian-boot@l.d.o rather than debian-testing@l.d.o at jello@d.o's
+       request.
+
+       * utils.py (parse_changes): calculate the number of lines once
+       with len() rather than max().
+
+       * jennifer (check_dsc): handle the .orig.tar.gz disappearing from
+       files, since check_dsc_against_db() deletes the .orig.tar.gz
+       entry.
+
+2003-11-13  Ryan Murray  <rmurray@debian.org>
+
+       * apt.conf: specify a src override file for debian-installer
+
+2003-11-10  James Troup  <james@nocrew.org>
+
+       * fernanda.py (strip_pgp_signature): new function - strips PGP
+       signature from a file and returns the modified contents of the
+       file in a string.
+       (display_changes): use it.
+       (read_dsc): likewise.
+
+2003-11-09  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: export accepted_autobuild table for unstable, and use
+       it to generate the incoming Packages/Sources rather than having apt
+       walk the directory.
+       * apt.conf.buildd: use exported table from cron.buildd to generate
+       Packages/Sources
+
+2003-11-07  James Troup  <james@nocrew.org>
+
+       * kelly: import errno.
+
+       * katie.py (Katie.build_summaries): sort override disparities.
+
+       * kelly (install): set dsc_component based on the .dsc's component
+       not a random binaries.
+
+2003-10-29  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.build_summaries): don't assume changes["source"]
+       exists since it might not.
+
+2003-10-20  James Troup  <james@nocrew.org>
+
+       * pseudo-packages.maintainers: update security.d.o to use
+       team@s.d.o at joy@'s request.
+
+2003-10-17  James Troup  <james@nocrew.org>
+
+       * jennifer (check_dsc): use .startswith rather than .find() == 0.
+
+2003-10-17  Martin Michlmayr  <tbm@cyrius.com>
+
+       * tea (chk_bd_process_dir): use .endswith rather than slice.
+
+2003-10-14  James Troup  <james@nocrew.org>
+
+       * tea (check_build_depends): new function.
+       (chk_bd_process_dir): likewise.  Validates build-depends in .dsc's
+       in the archive.
+       (main): update for new function.
+       (usage): likewise.
+
+       * katie.py (Katie.do_reject): sanitize variable names,
+       s/reject_filename/reason_filename/, s/fd/reason_fd/.  Move shared
+       os.close() to outside if clause.
+
+       * jennifer (check_dsc): check build-depends and
+       build-depends-indep by running them past apt_pkg.ParseSrcDepends.
+       Fold the ARRAY check into the same code block and tidy up it's
+       rejection message.
+       (check_changes): ensure that the Files field is non-empty.
+       Suggested by Santiago Vila <sanvila@unex.es>
+       (check_changes): normalize reject messages.
+       (check_dsc): instead of doing most of the checks inside a for loop
+       and an if, find the dsc_filename in a short loop over files first
+       and then do all the checks.  Add check for more than one .dsc in a
+       .changes which we can't handle.  Normalize reject messages.
+
+2003-10-13  James Troup  <james@nocrew.org>
+
+       * katie.conf (Dinstall::Reject::NoSourceOnly): set to true.
+       * katie.conf-non-US (Dinstall::Reject::NoSourceOnly): likewise.
+
+       * jennifer (check_files): Set 'has_binaries' and 'has_source'
+       variables while iterating over 'files'.  Don't regenerate it when
+       checking for source if source is mentioned.
+
+       Reject source only uploads if the config variable
+       Dinstall::Reject::NoSourceOnly is set.
+
+2003-10-03  James Troup  <james@nocrew.org>
+
+       * rene (main): add nasty hardcoded reference to debian-installer
+       so we detect NBS .udebs.
+
+2003-09-29  James Troup  <james@nocrew.org>
+
+       * apt.conf (old-proposed-updates): remove.
+       * apt.conf-non-US (old-proposed-updates): likewise.
+
+2003-09-24  James Troup  <james@nocrew.org>
+
+       * tea (check_files_not_symlinks): new function, ensure files
+       mentioned in the database aren't symlinks.  Includes code to
+       update any files that are like this to their real filenames +
+       location; commented out by though.
+       (usage): update for new function.
+       (main): likewise.
+
+2003-09-24  Anthony Towns  <ajt@debian.org>
+
+       * vars: external-overrides variable added
+       * cron.daily: Update testing/unstable Task: overrides from joeyh
+       managed external source.
+
+2003-09-22  James Troup  <james@nocrew.org>
+
+       * kelly (install): if we can't move the .changes into queue/done,
+       fail don't warn and carry on.  The old behaviour pre-dates NI and
+       doesn't make much sense now since jennifer checks both
+       queue/accepted and queue/done for any .changes files it's
+       processing.
+
+       * utils.py (move): don't throw exceptions on existing files or
+       can't overwrite, instead just fubar out.
+
+       * jennifer (check_dsc): also check Build-Depends-Indep for
+       ARRAY-lossage.  Noticed by Matt Zimmerman <mdz@debian.org>.
+
+2003-09-18  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.close_bugs): only log the bugs we've closed
+       once.
+
+       * kelly (main): log as 'kelly', not 'katie'.
+
+2003-09-16  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.check_binary_against_db): likewise noramlize.
+
+       * jennifer (check_changes): normalize reject message for "changes
+       file already exists" to be %s: <foo>.
+       (check_dsc): add a check for 'Build-Depends: ARRAY(<hex>)'
+       produced by broken dpkg-source in 1.10.11.  Tone down and
+       normalize rejection message for incompatible 'Format' version
+       numbers.
+       (check_diff): likewise tone down and normalize.
+
+2003-09-07  James Troup  <james@nocrew.org>
+
+       * utils.py (parse_changes): if dsc_whitespace_rules is false,
+       don't bomb out on bogus empty lines.
+       (build_file_list): check for changes["files"] earlier.  use Dict
+       to create files[name] dictionary.
+       (send_mail): don't bother validating arguments.
+       (check_signature): minor improvements to some of the rejection
+       messages including listing the key id of the key that wasn't found
+       in the keyring.
+       (wrap): new function.
+
+       * tea: add new check 'validate-indices' that ensures all files
+       mentioned in indices (Packages, Sources) files do in fact exist.
+
+       * catherine (poolize): use a local re_isadeb which handles legacy
+       (i.e. no architecture) style .deb filenames.
+
+       * rosamund: new script.
+
+       * rhona (check_binaries): when checking for binary packages not in
+       a suite, don't bother selecting files that already have a
+       last_used date.
+       (check_sources): likewise.
+
+       * rhona: change all SQL EXISTS sub-query clauses to use the
+       postgres suggested convention of "SELECT 1 FROM".
+       * andrea (main): likewise.
+       * tea (check_override): likewise.
+       * catherine (main): likewise.
+
+       * katie.conf (Suite): remove OldStable and Old-Proposed-Updates
+       entries and in other suites MustBeNewerThan's.
+       (SuiteMappings): likewise
+       * katie.conf-non-US: likewise.
+       * katie.conf-security: likewise.
+
+       * apt.conf-security: remove oldstable.
+       * apt.conf.stable: likewise.
+       * apt.conf.stable-non-US: likewise.
+       * cron.buildd-security: likewise.
+       * cron.daily-security: likewise.
+       * vars-security (suites): likewise.
+       * wanna-build/trigger.daily: likewise.
+
+       * claire.py (clean_symlink): move...
+       * utils.py (clean_symlink): here.
+
+       * claire.py (find_dislocated_stable): update accordingly.
+
+2003-08-16  Anthony Towns  <ajt@debian.org>
+
+       * katie.py (source_exists): expand the list of distributions
+       the source may exist in to include any suite that's mapped to
+       the destination suite (even transitively (a->b->c)). This should
+       unbreak binary uploads to *-proposed-updates.
+
+2003-08-09  Randall Donald  <rdonald@debian.org>
+
+       * lisa (recheck): change changes["distribution"].keys() to
+       Katie.pkg.changes...
+
+2003-08-08  Randall Donald  <rdonald@debian.org>
+
+       * katie.py: only tag bugs as fixed-in-experimental for
+       experimental uploads
+
+2003-07-26  Anthony Towns  <ajt@debian.org>
+
+       * katie.py (source_exists): add an extra parameter to limit the
+       distribution(s) the source must exist in.
+       * kelly, lisa, jennifer: update to use the new source_exists
+
+2003-07-15  Anthony Towns  <ajt@debian.org>
+
+       * ziyi: quick hack to support a FakeDI line in apt.conf to generate
+       checksums for debian-installer stuff even when it's just a symlink to
+       another suite
+
+       * apt.conf: add the FakeDI line
+
+2003-06-09  James Troup  <james@nocrew.org>
+
+       * kelly (check): make sure the 'file' we're looking for in 'files'
+       hasn't been deleted by katie.check_dsc_against_db().
+
+2003-05-07  James Troup  <james@nocrew.org>
+
+       * helena (time_pp): fix s/years/year/ typo.
+
+2003-04-29  James Troup  <james@nocrew.org>
+
+       * madison (usage): document -c/--component.
+
+       * madison (usage): Fix s/seperated/separated/.
+       * melanie (usage): likewise.
+       * jenna (usage): likewise.
+
+2003-04-24  James Troup  <james@nocrew.org>
+
+       * cron.daily-non-US: if there's nothing for kelly to install, say
+       so.
+
+       * jennifer (check_timestamps): print sys.exc_value as well as
+       sys.exc_type when capturing exceptions.  Prefix 'timestamp check
+       failed' with 'deb contents' to make it clearer what timestamp(s)
+       are being checked.
+
+2003-04-15  James Troup  <james@nocrew.org>
+
+       * cron.daily-non-US: only run kelly if there are some .changes
+       files in accepted.
+
+       * rene: add -m/--mode argument which can be either daily (default)
+       or full.  In daily mode only 'nviu' and 'nbs' checks are run.
+       Various changes to make this possible including a poor attempt at
+       splitting main() up a little.  De-hardcode suite numbers from SQL
+       queries and return quietly from do_nviu() if experimental doesn't
+       exist (i.e. non-US).  Hardcode pcmcia-cs as dubious NBS since it
+       is.
+
+       * debian/control (Depends): remove python-zlib as it's obsolete.
+
+       * charisma (main): don't slice the \n off strings when we're
+       strip()-ing it anyway.
+       * heidi (set_suite): likewise.
+       (process_file): likewise.
+       * natalie (process_file): likewise.
+
+2003-04-08  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.check_dsc_against_db): improve the speed of two
+       slow queries by using one LIKE '%foo%' and then matching against
+       '%s' or '/%s$' in python.  Also only join location when we need it
+       (i.e. the .orig.tar.gz query).  On auric, this knocks ~3s of each
+       query, so 6s for each sourceful package.
+
+       * cron.daily: invoke rene and send the report to ftpmaster.
+       * cron.daily-non-US: likewise.
+
+2003-03-14  James Troup  <james@nocrew.org>
+
+       * utils.py (send_mail): default filename to blank.
+       * amber (make_advisory): adapt.
+       * jennifer (acknowledge_new): likewise.
+       * katie.py (Katie.close_bugs): likewise.
+       (Katie.announce): likewise.
+       (Katie.accept): likewise.
+       (Katie.check_override): likewise.
+       (Katie.do_reject): likewise.
+       * kelly (do_reject): likewise.
+       (stable_install): likewise.
+       * lisa (do_bxa_notification): likewise.
+       * lauren (reject): likewise.
+       * melanie (main): likewise.
+
+       * rene (add_nbs): check any NBS packages against unstable to see
+       if they haven't been removed already.
+
+       * templates/katie.rejected: remove paragraph about rejected files
+       since they're o-rwx due to c-i-m and the uploader can't do
+       anything about them and shania will take care of them anyway.
+
+       * madison (usage): update usage example to use comma seperation.
+       * melanie (usage): likewise.
+
+       * utils.py (split_args): new function; splits command line
+       arguments either by space or comma (whichever is used).  Also has
+       optional-but-default DWIM spurious space detection to avoid
+       'command -a i386, m68k' problems.
+       (parse_args): use it.
+       * melanie (main): likewise.
+
+       * melanie (main): force the admin to tell someone if we're not
+       doing a rene-led removal (or closing a bug, which counts as
+       telling someone).
+
+2003-03-05  James Troup  <james@nocrew.org>
+
+       * katie.conf (Section): add embedded, gnome, kde, libdevel, perl
+       and python sections.
+       * katie.conf-security (Section): likewise.
+
+       * add_constraints.sql: add uid and uid_id_seq to grants.
+
+       * lisa (determine_new): also warn about adding overrides to
+       oldstable.
+
+       * madison (main): make the -S/--source-and-binary query obey
+       -s/--suite restrictions.
+
+2003-03-03  James Troup  <james@nocrew.org>
+
+       * madison (main): if the Archive_Maintenance_In_Progress lockfile
+       exists, warn the user that our output might seem strange.  (People
+       get confused by multiple versions in a suite which happens
+       post-kelly but pre-jenna.)
+
+2003-02-21  James Troup  <james@nocrew.org>
+
+       * kelly (main): we don't need to worry about StableRejector.
+
+       * melanie (main): sort versions with apt_pkg.VersionCompare()
+       prior to output.
+
+       * lauren: new script to manually reject packages from
+       proposed-updates.  Updated code from pre-NI kelly (nee katie).
+
+2003-02-20  James Troup  <james@nocrew.org>
+
+       * kelly (init): remove unused -m/--manual-reject argument.
+
+       * katie.py (Katie.force_reject): renamed from force_move to make
+       it more explicit what this function does.
+       (Katie.do_reject): update to match.
+
+       * utils.py (prefix_multi_line_string): add an optional argument
+       include_blank_lines which defaults to 0.  If non-zero, blank lines
+       will be includes in the output.
+
+       * katie.py (Katie.do_reject): don't add leading space to each line
+       of the reject message.  Include blank lines when showing the
+       message to the user.
+
+2003-02-19  Martin Michlmayr  <tbm@cyrius.com>
+
+       * utils.py (fix_maintainer): replace pointless re.sub() with
+       simple string format.
+
+2003-02-11  James Troup  <james@nocrew.org>
+
+       * lisa (edit_overrides): only strip-to-one-char and upper-case
+       non-numeric answers.  Fixes editing of items with indices >= 10;
+       noticed by Randall.
+       (edit_overrides): correct order of arguments to "not a valid
+       index" error message.
+
+       * jenna (cleanup): call resolve_arch_all_vs_any() rather than
+       remove_duplicate_versions(); thanks to aj for the initial
+       diagnosis.
+       (remove_duplicate_versions): correct how we return
+       dominant_versions.
+       (resolve_arch_all_vs_any): arch_all_versions needs to be a list of
+       a tuple rather than just a tuple.
+
+2003-02-10  James Troup  <james@nocrew.org>
+
+       * emilie: new script - sync fingerprint and uid tables with a
+       debian.org LDAP DB.
+
+       * init_pool.sql: new table 'uid'; contains user ids.  Reference it
+       in 'fingerprint'.
+
+       * db_access.py (get_or_set_uid_id): new function.
+
+       * jennifer (main): update locking to a) not used FCNTL (deprecated
+       in python >= 2.2) and b) acknowledge upstream's broken
+       implementation of lockf (see Debian bug #74777), c) try to acquire
+       the lock non-blocking.
+       * kelly (main): likewise.
+
+       * contrib/python_1.5.2-fcntl_lockf.diff: obsolete, removed.
+
+       * madison (main): only append the package to new_packages if it's
+       not already in there; fixes -S/--source-and-binary for cases where
+       the source builds a binary package of the same name.
+
+2003-02-10  Anthony Towns  <ajt@debian.org>
+
+       * madison (main): use explicit JOIN syntax for
+       -S/--source-and-binary queries to reduce the query's runtime from
+       >10 seconds to negligible.
+
+2003-02-08  James Troup  <james@nocrew.org>
+
+       * rene (main): in the NVIU output, append items to lists, not
+       extend them; fixes amusing suggestion that "g n u m e r i c" (sic)
+       should be removed.
+
+2003-02-07  James Troup  <james@nocrew.org>
+
+       * apt.conf (tree "dists/unstable"): Add bzip2-ed Packages and
+       Sources [aj].
+
+       * pseudo-packages.maintainers (bugs.debian.org): s/Darren
+       O. Benham/Adam Heath/.
+
+       * katie.conf (Suite::Stable::Version): bump to 3.0r1a.
+       (Suite::Stable::Description): update for 3.0r1a.
+       (Dinstall::SigningKeyIds): update for 2003 key [aj].
+
+       * utils.py (gpgv_get_status_output): rename from
+       get_status_output().
+
+       * neve (check_signature): use gpgv_get_status_output and Dict from
+       utils().  Add missing newline to error message about duplicate tokens.
+
+       * saffron (per_arch_space_use): also print space used by source.
+       (output_format): correct string.join() invocation.
+
+       * jennifer (check_signature): ignored duplicate EXPIRED tokens.
+
+2003-02-04  James Troup  <james@nocrew.org>
+
+       * cron.buildd: correct generation of Packages/Sources and grep out
+       non-US/non-free as well as non-free.
+
+2003-02-03  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: generate quinn-diff output with full Packages/Sources
+         files to get out-of-date vs. uncompiled right.
+       * apt.conf.buildd: no longer generate uncompressed files, as they
+         are generated in cron.buildd instead
+       * add -i option to quinn-diff to ignore binary-all packages
+       * apt.conf.buildd: remove and readd udeb to extensions.  If the udebs
+         aren't in the packages file, the arch that uploaded them will build
+         them anyways...
+
+2003-01-30  James Troup  <james@nocrew.org>
+
+       * rene (main): only print suggested melanie command when there's
+       some NBS to remove.
+
+2003-01-30  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: fix incorrectly inverted lockfile check
+
+2003-01-29  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: generate override.sid.all3.src
+       * apt.conf.buildd: use generated override.sid.all3.src
+
+2003-01-27  Martin Michlmayr  <tbm@cyrius.com>
+
+       * utils.py (get_status_output): moved from jennifer.
+       (Dict): likewise.
+       (check_signature): likewise.
+
+       * jennifer (get_status_output): moved to utils.py.
+       (Dict): likewise.
+       (check_signature): likewise.
+
+       * utils.py (check_signature): add an argument to specifiy which
+       function to call when an error was found.
+       (check_signature): document this function better.
+
+       * jennifer (check_files): pass the reject function as an argument
+       to utils.check_signature.
+       (process_it): likewise.
+
+2003-01-20  James Troup  <james@nocrew.org>
+
+       * rene (main): lots of changes to improve the output and make it
+       more useful.
+
+       * katie.py (Katie.check_override): make the override messages
+       clearer (hopefully).
+
+2002-12-26  James Troup  <james@nocrew.org>
+
+       * ziyi (usage): document the ability to pass suite(s) as
+       argument(s).
+       (main): read apt.conf after checking for -h/--help.
+
+       * tea (main): take the check to run as an argument.
+
+       * saffron.R: R script to graph daily install runs.
+
+       * saffron: new script; various stats functions.
+
+       * rhona (main): connect to the database after checking for -h/--help.
+
+       * neve (do_da_do_da): if no -a/--action option is given, bail out.
+
+       * melanie (main): sort versions with utils.arch_compare_sw().
+
+       * madison (usage): alphabetize order of options.
+       * melanie (usage): likewise.
+
+       * kelly (usage): fix usage short description (we aren't dinstall).
+
+       * julia (usage): fix usage description and alphabetize order of
+       options.
+
+       * jeri (usage): fix usage short description.
+
+       * jennifer (main): move --help and --version checks from here...
+       (init): to here so that they work with an empty katie.conf.
+       * kelly: likewise.
+
+       * alyson (usage): new function.
+       (main): use it.
+       * andrea: likewise.
+       * ashley: likewise.
+       * cindy: likewise.
+       * denise: likewise.
+       * helena: likewise.
+       * neve: likewise.
+       * rene: likewise.
+       * rose: likewise.
+       * tea: likewise.
+
+       * apt.conf.stable (tree "dists/stable"): add missing ExtraOverride
+       entry that caused tasks to be omitted from 3.0r1.
+
+2002-12-10  James Troup  <james@nocrew.org>
+
+       * jennifer (check_files): sanity check the Depends field to ensure
+       it's non-empty if present since apt chokes on an empty one.
+       Thanks to Ryan Murray for the idea.
+
+2002-12-08  James Troup  <james@nocrew.org>
+
+       * katie.conf-security (Helena::Directories): new; include accepted
+       in addition to byhand and new.
+
+       * helena (process_changes_files): use utils.arch_compare_sw().
+       Justify things based on the longest [package, version,
+       architecture].  Reduce '[note]' to '[N]' to save space, and remove
+       the commas in architecture and version lists for the same reason.
+       (main): make directories we process configurable through
+       Helena::Directories in the config file; if that doesn't exist
+       default to the old hardcoded values (byhand & new).
+
+       * utils.py (arch_compare_sw): moved here from madison.
+       * madison (main): adjust to compensate.
+
+2002-12-06  James Troup  <james@nocrew.org>
+
+       * ziyi (main): fix "suite foo not in apt.conf" msg to use the
+       right filename.
+
+2002-12-05  James Troup  <james@nocrew.org>
+
+       * katie.conf-non-US (Julia::KnownPostgres): add 'udmsearch'.
+
+2002-11-28  Randall Donald  <rdonald@debian.org>
+
+       * fernanda.py (read_control): fix typo of 'Architecture'.
+
+2002-11-26  James Troup  <james@nocrew.org>
+
+       * lisa (check_pkg): call less with '-R' so we see the colour from
+       Randall's fernanda changes.
+
+       * neve (process_sources): if Directory points to a legacy location
+       but the .dsc isn't there; assume it's broken and look in the pool.
+       (update_section): new, borroed from alyson.
+       (do_da_do_da): use it.
+       (process_packages): add suite_it to the cache key used for
+       arch_all_cache since otherwise we only add a package to the first
+       suite it's in and ignore any subsequent ones.
+
+       * katie.conf-non-US (Location): fixed to reflect reality (all
+       suites, except old-proposed-updates (which is legacy-mixed)) are
+       pool.
+
+       * utils.py (try_with_debug): wrapper for print_exc().
+       * jenna (main): use it.
+       * neve (main): likewise.
+
+2002-11-25  Randall Donald  <rdonald@debian.org>
+
+       * fernanda.py (main): added -R to less command line for raw control
+       character support to print colours
+       (check_deb): Instead of running dpkg -I on deb file, call
+       output_deb_info, the new colourized control reporter.
+       (check_dsc): add call to colourized dsc info reader, read_dsc, instead
+       of printing out each .dsc line.
+       (output_deb_info): new function. Outputs each key/value pair from
+       read_control except in special cases where we highlight section,
+       maintainer, architecture, depends and recommends.
+       (create_depends_string): new function. Takes Depends tree and looks
+       up it's compontent via projectb db, colourizes and constructs a
+       depends string in original order.
+       (read_dsc): new function. reads and parses .dsc info via
+       utils.parse_changes. Build-Depends and Build-Depends-Indep are
+       colourized.
+       (read_control): new function. reads and parses control info via
+       apt_pkg. Depends and Recommends are split in to list structures,
+       Section and Architecture are colourized. Maintainer is colourized
+       if it has a localhost.localdomain address.
+       (split_depends): new function. Creates a list of lists of
+       dictionaries of depends (package,version relation). Top list is
+       colected from comma delimited items. Sub lists are | delimited.
+       (get_comma_list): new function. splits string input among commas
+       (get_or_list): new function. splits string input among | delimiters
+       (get_depends_parts): new function. Creates dictionary of package name
+       and version relation from dependancy string.
+       Colours for section and depends are per component. Unfound depends
+       are in bold. Lookups using version info is not supported yet.
+
+2002-11-22  James Troup  <james@nocrew.org>
+
+       * katie.conf-security (Julia::KnownPostgres): add 'www-data' and
+       'udmsearch'.
+
+       * amber (make_advisory): string.atol() is deprecated and hasn't
+       been ported to string methods.  Use long() instead.
+
+       * init_pool.sql: explicitly specify the encoding (SQL_ASCII) when
+       creating the database since we'll fail badly if it's created with
+       e.g. UNICODE encoding.
+
+       * rose (main): AptCnf is a global.
+
+       * neve (get_location_path): new function determines the location
+       from the the first (left-most) directory of a Filename/Directory.
+       (process_sources): don't need 'location' anymore.  Use
+       utils.warn().  Use the Directory: field for each package to find
+       the .dsc.  Use get_location_path() to determine the location for
+       each .dsc.
+       (process_packages): do't need 'location' anymore.  Use
+       utils.warn().  Use get_location_path().
+       (do_sources): don't need 'location', drop 'prefix' in favour of
+       being told the full path to the Sources file, like
+       process_packages().
+       (do_da_do_da): main() renamed, so that main can call us in a
+       try/except.  Adapt for the changes in do_sources() and
+       process_packages() above.  Assume Sources and Packages file are in
+       <root>/dists/<etc.>.  Treat pool locations like we do legacy ones.
+
+       * katie.conf-security (Location): fixed to reflect reality (all
+       suites are pool, not legacy).
+
+       * utils.py (print_exc): more useful (i.e. much more verbose)
+       traceback; a recipe from the Python cookbook.
+       * jenna (main): use it.
+       * neve (main): likewise.
+
+2002-11-19  James Troup  <james@nocrew.org>
+
+       * kelly (install): fix brain-damaged CopyChanges/CopyKatie
+       handling which was FUBAR for multi-suite uploads.  Now we just
+       make a dictionary of destinations to copy to and iterate over
+       those.
+
+       * fernanda.py (check_deb): run linda as well as lintian.
+
+2002-10-21  James Troup  <james@nocrew.org>
+
+       * melanie (main): change X-Melanie to X-Katie and prefix it with
+       'melanie '.
+
+       * lisa (main): prefix X-Katie with 'lisa '.
+
+       * jennifer (clean_holding): fix typo in string method changes;
+       s/file.find(file/file.find(/.
+
+       * cron.daily: invoke helena and send the report to ftpmaster.
+       * cron.daily-non-US: likewise.
+
+2002-10-16  James Troup  <james@nocrew.org>
+
+       * kelly (check): call reject() with a blank prefix when parsing
+       the return of check_dsc_against_db() since it does its own
+       prefix-ing.
+
+       * rose: new script; only handles directory creation initally.
+
+       * katie.conf (Dinstall::NewAckList): obsolete, removed.
+       * katie.conf-non-US (Dinstall::NewAckList): likewise.
+
+2002-10-06  James Troup  <james@nocrew.org>
+
+       * rene (main): remove bogus argument handling.
+
+       * kelly: katie, renamed.
+       * cron.daily: adapt for katie being renamed to kelly.
+       * cron.daily-non-US: likewise.
+       * amber (main): likewise.
+
+       * Changes for python 2.1.
+
+       * kelly: time.strftime no longer requires a second argument of
+       "time.localtime(time.time())".
+       * logging.py: likewise.
+       * rhona: likewise.
+       * shania (init): likewise.
+
+       * amber: use augmented assignment.
+       * catherine (poolize): likewise.
+       * claire.py (fix_component_section): likewise.
+       * halle (check_changes): likewise.
+       * helena: likewise.
+       * jenna: likewise.
+       * jennifer: likewise.
+       * jeri: likewise.
+       * katie.py: likewise.
+       * kelly: likewise.
+       * lisa: likewise.
+       * madison (main): likewise.
+       * melanie: likewise.
+       * natalie: likewise.
+       * neve: likewise.
+       * rhona: likewise.
+       * tea: likewise.
+       * utils.py: likewise.
+       * ziyi: likewise.
+
+       * amber: use .endswith.
+       * fernanda.py: likewise.
+       * halle (main): likewise.
+       * jennifer: likewise.
+       * jeri: likewise.
+       * katie.py: likewise.
+       * kelly: likewise.
+       * lisa: likewise.
+       * neve: likewise.
+       * shania (main): likewise.
+       * utils.py: likewise.
+
+       * alyson: use string methods.
+       * amber: likewise.
+       * andrea: likewise.
+       * ashley: likewise.
+       * catherine: likewise.
+       * charisma: likewise.
+       * claire.py: likewise.
+       * db_access.py: likewise.
+       * denise: likewise.
+       * halle: likewise.
+       * heidi: likewise.
+       * helena: likewise.
+       * jenna: likewise.
+       * jennifer: likewise.
+       * jeri: likewise.
+       * julia: likewise.
+       * katie.py: likewise.
+       * kelly: likewise.
+       * lisa: likewise.
+       * logging.py: likewise.
+       * madison: likewise.
+       * melanie: likewise.
+       * natalie: likewise.
+       * neve: likewise.
+       * rene: likewise.
+       * tea: likewise.
+       * utils.py: likewise.
+       * ziyi: likewise.
+
+2002-09-20  Martin Michlmayr  <tbm@cyrius.com>
+
+       * utils.py (parse_changes): use <string>.startswith() rather than
+       string.find().
+
+2002-08-27  Anthony Towns  <ajt@debian.org>
+
+       * katie.py (in_override_p): when searching for a source override,
+       and the dsc query misses, search for both udeb and deb overrides
+       as well. Should fix the UNACCEPT issues with udebs.
+
+2002-08-24  James Troup  <james@nocrew.org>
+
+       * melanie (main): remove gratuitous WHERE EXISTS sub-select from
+       source+binary package finding code which was causing severe
+       performance degradation with postgres 7.2.
+
+2002-08-14  James Troup  <james@nocrew.org>
+
+       * julia (main): use the pwd.getpwall() to get system user info
+       rather than trying to read a password file.  Add a -n/--no-action
+       option.
+
+       * cron.hourly: julia no longer takes any arguments.
+       * cron.hourly-non-US: likewise.
+
+2002-08-07  James Troup  <james@nocrew.org>
+
+       * katie (install): handle multi-suite uploads when CopyChanges
+       and/or CopyKatie are in use, ensuring we only copy stuff once.
+
+2002-08-01  Ryan Murray  <rmurray@debian.org>
+
+       * wanna-build/trigger.daily: initial commit, with locking
+       * cron.buildd: add locking against daily run
+
+2002-07-30  James Troup  <james@nocrew.org>
+
+       * melanie (main): readd creation of suite_ids_list so melanie is
+       remotely useful again.
+
+       * katie.conf: adopt for woody release; diable
+       StableDislocationSupport, add oldstable, adjust other suites and
+       mappings, fix up location.
+       * katie.conf-non-US: likewise.
+       * katie.conf-security: likewise.
+
+       * apt.conf.stable: adapt for woody release; add oldstable, adjust
+       stable.
+       * apt.conf.stable-non-US: likewise.
+
+       * apt.conf-security: adapt for woody release; adding oldstable,
+       oldstable, adjust stable and testing.
+       * cron.daily-security: likewise.
+       * cron.buildd-security: likewise.
+
+       * apt.conf: adapt for woody release; rename woody-proposed-updates
+       to testing-proposed-updates and proposed-updates to
+       old-proposed-updates.
+       * apt.conf-non-US: likewise.
+
+       * vars-non-US (copyoverrides): add sarge.
+       * vars (copyoverrides): likewise.
+
+       * vars-security (suites): add oldstable.
+
+2002-07-22  Ryan Murray  <rmurray@debian.org>
+
+       * apt.conf.security-buildd: use suite codenames instead of
+         distnames.
+
+2002-07-16  James Troup  <james@nocrew.org>
+
+       * denise (main): fix filenames for testing override files.
+
+2002-07-14  James Troup  <james@nocrew.org>
+
+       * jennifer (process_it): call check_md5sums later so we can check
+       files in the .dsc too
+       (check_md5sums): check files in the .dsc too.  Check both md5sum
+       and size.
+
+       * melanie (main): use parse_args() and join_with_commas_and() from
+       utils.  If there's nothing to do, say so and exit, don't ask for
+       confirmation etc.
+
+       * amber (join_with_commas_and): moved from here to ...
+       * utils.py (join_with_commas_and): here.
+
+2002-07-13  James Troup  <james@nocrew.org>
+
+       * madison (main): use parse_args() from utils.  Support
+       -c/--component.
+
+       * jenna (parse_args): moved from here to ...
+       * utils.py (parse_args): here.
+
+       * katie.conf (Architectures): minor corrections to the description
+       for arm, mips and mipsel.
+       * katie.conf-non-US (Architectures): likewise.
+       * katie.conf-security (Architectures): likewise.
+
+       * cron.daily-security: use natalie's new -a/--add functionality to
+       flesh out the security overrides.
+
+2002-07-12  James Troup  <james@nocrew.org>
+
+       * cron.buildd (ARCHS): add arm.
+
+       * katie.conf: 2.2r7 was released.
+       * katie.conf-non-US: likewise.
+
+       * utils.py (parse_changes): handle a multi-line field with no
+       starting line.
+
+2002-06-25  James Troup  <james@nocrew.org>
+
+       * templates/amber.advisory (To): add missing email address since
+       __WHOAMI__ is only a name.
+
+       * katie.conf-security (Melane::LogFile): correct to go somewhere
+       katie has write access to.
+       (Location::/org/security.debian.org/ftp/dists/::Suites): add
+       Testing.
+
+       * natalie: add support for -a/-add which adds packages only
+       (ignoring changes and deletions).
+
+       * katie.py (Katie.announce): Dinstall::CloseBugs is a boolean so
+       use FindB, not get.
+
+2002-06-22  James Troup  <james@nocrew.org>
+
+       * jennifer (check_files): validate the package name and version
+       field.  If 'Package', 'Version' or 'Architecture' are missing,
+       don't try any further checks.
+       (check_dsc): likewise.
+
+       * utils.py (re_taint_free): add '~' as a valid character.
+
+2002-06-20  Anthony Towns  <ajt@debian.org>
+
+       * katie.conf-non-US: add OverrideSuite for w-p-u to allow uploads
+
+2002-06-09  James Troup  <james@nocrew.org>
+
+       * jennifer (check_files): reduce useless code.
+
+       * cron.daily-security: run symlinks -dr on $ftpdir.
+
+       * vars-security (ftpdir): add.
+
+2002-06-08  James Troup  <james@nocrew.org>
+
+       * neve (update_override_type): transaction is handled higher up in
+       main().
+       (update_priority): likewise.
+       (process_sources): remove code that makes testing a duplicate of
+       stable.
+       (process_packages): likewise.
+
+       * templates/amber.advisory: add missing mail headers.
+
+       * cron.daily-security: also call apt-ftparchive clean for
+       apt.conf.buildd-security.
+       * cron.weekly: likewise.
+
+       * amber (do_upload): write out a list of source packages (and
+       their version) uploaded for testing.
+       (make_advisory): add more Subst mappings for the mail headers.
+       (spawn): check for suspicious characters in the command and abort
+       if their found.
+
+2002-06-07  James Troup  <james@nocrew.org>
+
+       * ziyi (main): remove the 'nonus'/'security' hacks and use
+       Dinstall::SuiteSuffix (if it exists) instead.  Always try to write
+       the lower level Release files, even if they don't exist.  fubar
+       out if we can't open a lower level Release file for writing.
+
+       * katie.conf-non-US (Dinstall): add SuiteSuffix, used to simplify
+       ziyi.
+       * katie.conf-security (Dinstall): likewise.
+
+       * amber (do_upload): renamed from get_file_list().  Determine the
+       upload host from the original component.
+       (init): Add a -n/--no-action option.  Fix how we get changes_files
+       (i.e. from the return value of apt_pkg.ParseCommandLine(), not
+       sys.argv).  Add an Options global.
+       (make_advisory): support -n/--no-action.
+       (spawn): likewise.
+       (main): likewise.
+       (usage): document -n/--no-action.
+
+       * cron.buildd-security: fix path to Packages-arch-specific in
+       quinn-diff invocation.
+
+       * katie.conf-security (Dinstall::AcceptedAutoBuildSuites): change
+       to proper suite names (i.e. stable, testing) rather than codenames
+       (potato, woody).
+       (Dinstall::DefaultSuite): likewise.
+       (Suite): likewise.
+       (Location::/org/security.debian.org/ftp/dists/::Suites): likewise.
+       * vars-security (suites): likewise.
+       * apt.conf-security: likewise.
+
+       * katie.conf-security (Component): add "updates/" prefix.
+       (Suite::*::Components): likewise.
+       (ComponentMappings): new; map all {ftp-master,non-US} components
+       -> updates/<foo>.
+
+       * katie.conf-security (Natalie): removed; the options have
+       defaults and ComponentPosition is used by alyson which doesn't
+       work on security.d.o.
+       (Amber): replace UploadHost and UploadDir with ComponentMappings
+       which is a mapping of components -> URI.
+       (Suite::*::CodeName): strip bogus "/updates" suffix hack.
+       (SuiteMappings): use "silent-map" in preference to "map".
+
+       * cron.unchecked-security: fix call to cron.buildd-security.
+
+       * cron.daily-security: map local suite (stable) -> override suite
+       (potato) when fixing overrides.  Correct component in natalie call
+       to take into account "updates/" prefix.  Fix cut'n'waste in
+       override.$dist.all3 generation, the old files weren't being
+       removed, so they were endlessly growing.
+
+       * neve (main): don't use .Find for the CodeName since we require
+       it.  Location::*::Suites is a ValueList.
+       (check_signature): ignore duplicate SIGEXPIRED tokens.  Don't bomb
+       out on expired keys, just warn.
+       (update_override_type): new function; lifted from alyson.
+       (update_priority): likewise.
+       (main): use update_{override_type,priority}().
+
+       * jennifer (check_distributions): remove redunant test for
+       SuiteMappings; ValueList("does-not-exist") returns [] which is
+       fine.  Add support for a "silent-map" type which doesn't warn
+       about the mapping to the user.
+       (check_files): add support for ComponentMappings, similar to
+       SuiteMappings, but there's no type, just a source and a
+       destination and the original component is stored in "original
+       component".
+       * katie.py (Katie.dump_vars): add "original component" as an
+       optionsal files[file] dump variable.
+
+       * claire.py (find_dislocated_stable): dehardcode 'potato' in SQL
+       query.  Add support for section-less legacy locations like current
+       security.debian.org through YetAnotherConfigBoolean
+       'LegacyStableHasNoSections'.
+       * katie.conf-security (Dinstall): LegacyStableHasNoSections is true.
+
+       * utils.py (real_arch): moved here from ziyi.
+       * ziyi (real_arch): moved to utils.py.
+       * ziyi (main): likewise.
+
+       * claire.py (find_dislocated_stable): use real_arch() with
+       filter() to strip out source and all.
+       * neve (main): likewise.
+       * rene (main): likewise.
+       * jeri (parse_packages): likewise.
+
+2002-06-06  James Troup  <james@nocrew.org>
+
+       * tea (check_missing_tar_gz_in_dsc): modifed patch from Martin
+       Michlmayr <tbm@cyrius.com> to be more verbose about what we're
+       doing.
+
+2002-05-23  Martin Michlmayr  <tbm@cyrius.com>
+
+       * jeri (check_joey): check if the line contains two elements
+       before accessing the second.  Also, strip trailing spaces as well
+       as the newline.
+       * halle (check_joey): likewise.
+
+2002-06-05  James Troup  <james@nocrew.org>
+
+       * cron.unchecked-security: new file; like cron.unchecked but if
+       there's nothing to do exit so we don't call cron.buildd-security.
+
+       * apt.conf.buildd-security: new file.
+
+       * vars (archs): alphabetize.
+       * vars-non-US (archs): likewise.
+
+       * vars-security: add unchecked.
+
+       * madison (main): reduce rather bizarrely verbose and purposeless
+       code to print arches to a simple string.join.
+
+       * katie.conf (Suites::Unstable): add UdebComponents, a new
+       valuelist of suites, used by jenna to flesh out the list of
+       <suite>_main-debian-installer-binary-<arch>.list files generated.
+       (Dinstall): add StableDislocationSupport, a new boolean used by
+       jenna to enable or disable stable dislocation support
+       (i.e. claire), as true.
+
+       * katie.conf (Dinstall): add StableDislocationSupport, a new
+       boolean used by jenna to enable or disable stable dislocation
+       support (i.e. claire), as true.
+       * katie.conf-non-US: likewise.
+       * katie.conf-security: likewise.
+
+       * cron.daily-security: generate .all3 overrides for the buildd
+       support.  Freshen a local copy of Packages-arch-specific from
+       buildd.debian.org.
+
+       * claire.py (find_dislocated_stable): disable the support for
+       files in legacy-mixed locations since none of the Debian archives
+       have any anymore.
+
+       * helena: new script; produces a report on NEW and BYHAND
+       packages.
+
+       * jenna: rewritten from scratch to fix speed problems.  Run time
+       on auric goes down from 31.5 minutes to 3.5 minutes.  Of that 3.5
+       minutes, 105 seconds are the monster query and 70 odd seconds is
+       claire.
+
+       * apt.conf.buildd (Default): remove MaxContentsChange as it's
+       irrelevant.
+
+2002-06-05  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd-security: new file.
+
+2002-06-05  Matt Kraai  <kraai@alumni.cmu.edu>
+
+       * denise (list): take a file argument and use it.
+       (main): don't abuse sys.stdout, just write to the file.
+
+       * claire.py (usage): Fix misspelling.
+       (clean_symlink): Simplify.
+       (find_dislocated_stable): Avoid unnecessary work.
+
+2002-05-29  James Troup  <james@nocrew.org>
+
+       * cameron: removed; apt-ftparchive can simply walk the directory.
+
+2002-05-26  Anthony Towns  <ajt@debian.org>
+
+       * katie.conf{,-non-US}: Map testing to testing-proposed-updates
+       for the autobuilders.
+
+2002-05-24  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: update override files before running apt-ftparchive
+
+2002-05-23  Martin Michlmayr  <tbm@cyrius.com>
+
+       * amber (main): remove extra space in prompt.
+
+       * utils.py (validate_changes_file_arg): use original filename in
+       error messages.
+
+       * jeri (check_joey): close file after use.
+       (parse_packages): likewise.
+       (main): setup debug option properly.
+
+       * melanie (main): remove unused packages variable and simplify the
+       code to build up con_packages by using repr().
+
+2002-05-23  James Troup  <james@nocrew.org>
+
+       * lisa (recheck): when we reject, also return 0 so the package is
+       skipped.
+       (sg_compare): fix note sorting.
+       (recheck): remove the .katie file after rejection.
+
+       * katie.py (Katie.accept): accepted auto-build support take 3;
+       this time adding support for security.  Security needs a) non-pool
+       files copied rather than symlinked since accepted is readable only
+       by katie/security and www-data needs to be able to see the files,
+       b) needs per-suite directories.  SpecialAcceptedAutoBuild becomes
+       AcceptedAutoBuildSuites and is a ValueList containing the suites.
+       SecurityAcceptedAutoBuild is a new boolean which controls whether
+       or not normal or security style is used.  The unstable_accepted
+       table was renamed to accepted_autobuild and a suite column added.
+       Also fix a bug noticed by Ryan where an existent orig.tar.gz
+       didn't have it's last_used/in_accepted flags correctly updated.
+       * katie (install): likewise.
+       * rhona (clean_accepted_autobuild): likewise.
+
+2002-05-22  James Troup  <james@nocrew.org>
+
+       * lisa (sort_changes): new function; sorts changes properly.
+       Finally.
+       (sg_compare): new function; helper for sort_changes().  Sorts by
+       have note and time of oldest upload.
+       (indiv_sg_compare): new function; helper for sort_changes().
+       Sorts by source version, have source and filename.
+       (main): use sort_changes().
+       (changes_compare): obsoleted; removed.
+
+2002-05-20  James Troup  <james@nocrew.org>
+
+       * rhona (clean_accepted_autobuild): don't die if a file we're
+       trying to remove doesn't exist.  Makes rhona more friendly to
+       katie/katie.py crashes/bugs without any undue cost.
+
+2002-05-19  James Troup  <james@nocrew.org>
+
+       * lisa (main): if sorting a large number of changes give some
+       feedback.
+       (recheck): new function, run the same checks (modulo NEW,
+       obviously) as katie does, if they fail do the standard
+       reject/skip/quit dance.
+       (do_pkg): use it.
+
+       * katie (install): don't try to unlink the symlink in the
+       AcceptedAutoBuild support if the destination is not a symlink (or
+       doesn't exist).  Avoids unnecessary bombs on previous partial
+       accepts and will still bomb hard if the file exists and isn't a
+       symlink.
+
+       * utils.py: blah, commands _is_ used when the mail stuff isn't
+       commented out like it is in my test environment.
+
+       * lisa (changes_compare): "has note" overrides everything else.
+       Use .katie files rather than running parse_changes, faster and
+       allows "has note" to work.  Correct version compare, it was
+       reversed.  Ctime check should only kick in if the source packages
+       are not the same.
+       (print_new): print out and return any note.  Rename 'ret_code' to
+       'broken'.
+       (edit_new): renamed from spawn_editor.  Don't leak file
+       descriptors.  Clean up error message if editor fails.
+       (edit_note): new function, allows one to edit notes.
+       (do_new): add note support, editing and removing.
+       (init): kill -s/--sort; with notes we always want to use our
+       sorting method.
+       (usage): likewise.
+
+       * katie.py (Katie.dump_vars): add "lisa note" as an optional
+       changes field.
+
+       * utils.py (build_file_list): rename 'dsc' to 'is_a_dsc' and have
+       it default to 0.  Adapt tests to assume it's boolean.
+       * fernanda.py (check_changes): adjust call appropriately.
+       * halle (check_changes): likewise.
+       * jennifer (check_changes): likewise.
+       * jeri (check_changes): likewise.
+       * shania (flush_orphans): likewise.
+
+       * jennifer (check_dsc): pass is_a_dsc by name when calling
+       build_file_list() for clarity.
+       * shania (flush_orphans): likewise.
+       * tea (check_missing_tar_gz_in_dsc): likewise.
+
+       * jennifer (check_dsc): pass dsc_whitespace_rules by name when
+       calling parse_changes() for clarity.
+       * tea (check_dscs): likewise.
+
+       * utils.py (parse_changes): make dsc_whitespace_rules default to
+       not true.
+       * halle (check_changes): adjust call appropriately.
+       * jennifer (check_changes): likewise.
+       * jeri (check_changes): likewise.
+       * lisa (changes_compare): likewise.
+       * utils.py (changes_compare): likewise.
+       * melanie (main): likewise.
+       * shania (flush_orphans): likewise.
+       * fernanda.py (check_changes): likewise.
+
+2002-05-18  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.dump_vars): make the .katie file unreadable,
+       it's not useful and by and large a duplication of information
+       available in readable format in other files.
+
+2002-05-16  Ryan Murray  <rmurray@debian.org>
+
+       * melanie: Dir::TemplatesDir -> Dir::Templates
+
+2002-05-15  Ryan Murray  <rmurray@debian.org>
+
+       * cameron: correct the use of os.path.join
+
+2002-05-15  Anthony Towns  <ajt@debian.org>
+
+       * ziyi: Update to match the new format for Architectures/Components
+       in katie.conf.
+
+2002-05-14  James Troup  <james@nocrew.org>
+
+       * amber: new script; 'installer' wrapper script for the security
+       team.
+
+       * katie.py (Katie.announce): remove unused 'dsc' local
+       variable. (pychecker)
+
+       * ziyi: pre-define AptCnf and out globals to None. (pychecker)
+
+       * neve: don't import sys, we don't use it. (pychecker)
+       (check_signature): fix return type mismatch. (pychecker)
+
+       * utils.py: don't import commands, we don't use it.  (pychecker)
+
+       * katie (install): SpecialAcceptedAutoBuild is a boolean.
+
+       * katie.py (Katie.dump_vars): don't store "oldfiles", it's
+       obsoleted by the change to "othercomponents" handling in jennifer
+       detailed below.
+       (Katie.cross_suite_version_check): new function; implements
+       cross-suite version checking rules specified in the conf file
+       while also enforcing the standard "must be newer than target
+       suite" rule.
+       (Katie.check_binary_against_db): renamed, since it's invoked once
+       per-binary, "binaries" was inaccurate.  Use
+       cross_suite_version_check() and don't bother with the "oldfiles"
+       rubbish as jennifer works out "othercomponents" herself now.
+       (Katie.check_source_against_db): use cross_suite_version_check().
+
+       * katie (check): the version and file overwrite checks
+       (check_{binary,source,dsc}_against_db) are not per-suite.
+
+       * jennifer (check_files): less duplication of
+       'control.Find("Architecture", "")' by putting it in a local
+       variable.
+       (check_files): call check_binary_against_db higher up since it's
+       not a per-suite check.
+       (check_files): get "othercomponents" directly rather than having
+       check_binary_against_db do it for us.
+
+       * heidi (main): 'if x:', not 'if x != []:'.
+       * katie.py (Katie.in_override_p): likewise.
+       (Katie.check_dsc_against_db): likewise.
+       * natalie (main): likewise.
+       * rene (main): likewise.
+       * ziyi (real_arch): likewise.
+
+       * alyson (main): Suite::%s::Architectures, Suite::%s::Components
+       and OverrideType are now value lists, not lists.
+       * andrea (main): likewise.
+       * cindy (main): likewise.
+       * claire.py (find_dislocated_stable): likewise.
+       * denise (main): likewise.
+       * jenna (main): likewise.
+       * jennifer (check_distributions): likewise.
+       (check_files): likewise.
+       (check_urgency): likewise (Urgency::Valid).
+       * jeri (parse_packages): likewise.
+       * neve (main): likewise (and Location::%s::Suites).
+       * rene (main): likewise.
+
+2002-05-13  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.check_source_against_db): correct case of reject
+       message to be consistent with binary checks.
+
+       * jennifer (get_status_output): don't leak 2 file descriptors per
+       invocation.
+       (check_signature): add missing '\n' to "duplicate status token"
+       error message.
+
+2002-05-09  James Troup  <james@nocrew.org>
+
+       * utils.py (validate_changes_file_arg): new function; validates an
+       argument which should be a .changes file.
+       * ashley (main): use it.
+       * lisa (main): likewise.
+
+       * katie.py (Katie.check_dsc_against_db): since there can be more
+       than one .orig.tar.gz make sure we don't assume the .orig.tar.gz
+       entry still exists in files.
+
+       * jennifer (check_dsc): handle the .orig.tar.gz disappearing from
+       files, since check_dsc_against_db() deletes the .orig.tar.gz
+       entry.
+
+       * cameron: cleanups.
+
+       * utils.py (changes_compare): change sort order so that source
+       name and source version trump 'have source'; this should fix
+       UNACCEPT problems in katie where -1 hppa+source & i386, -2
+       i386&source & hppa lead to -1 i386 unaccept.  Problem worked out
+       by Ryan.
+
+       * lisa (main): allow the arguments to be .katie files too.
+
+2002-05-07  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: add s390 to arch list again
+
+2002-05-05  Ryan Murray  <rmurray@debian.org>
+
+       * cron.buildd: new script, update w-b database from unstable_accepted
+       table
+       * cameron: new script, take list in unstable_accepted and write out
+       a file list for apt-ftparchive
+       * apt.conf.buildd: new apt configuration for Packages/Sources for
+       unstable_accepted
+       * vars: add s390 to arch list.
+
+2002-05-03  James Troup  <james@nocrew.org>
+
+       * neve (main): don't hard code the calling user as that doesn't
+       work with modern postgres installs.  Fix psql invocation for
+       init_pool.sql (database name required).  Dont' hard code the
+       database name.
+       (process_sources): add support for fingerprint and install_date.
+       (process_packages): add support for fingerprint.
+       (do_sources): pass in the directory, fingerprint support needs it.
+       (get_status_output): borrowed from jennifer.
+       (reject): likewise.
+       (check_signature): likewise.
+
+       * katie (install): only try to log urgencies if Urgency_Logger is
+       defined.
+       (main): only initialize Urgency_Logger is Dir::UrgencyLog is
+       defined; only close Urgency_Logger if it's defined.
+
+       * catherine (poolize): adapt for Dir rationalization.
+       * claire.py (find_dislocated_stable): likewise.
+       * denise (main): likewise.
+       * halle (check_joey): likewise.
+       * jenna: likewise.
+       * jennifer: likewise.
+       * jeri: likewise.
+       * katie.py: likewise.
+       * katie: likewise.
+       * lisa (do_bxa_notification): likewise.
+       * logging.py (Logger.__init__): likewise
+       * rene (main): likewise.
+       * rhona (clean): likewise.
+       * shania (init): likewise.
+       * tea: likewise.
+       * ziyi: likewise.
+
+       * lisa (add_overrides): Dinstall::BXANotify is a boolean, use
+       FindB, not FindI.
+
+       * rhona (clean_accepted_autobuild): SpecialAcceptedAutoBuild is a
+       boolean, use FindB, not get.
+
+       * katie.py (Katie.check_dsc_against_db): ignore duplicate
+       .orig.tar.gz's which are an exact (size/md5sum) match.
+
+       * ashley (main): really allow *.katie files as arguments too;
+       noticed by aj.
+
+       * sql-aptvc.cpp: postgres.h moved to a "server" subdirectory.
+
+2002-05-03  Anthony Towns  <ajt@debian.org>
+
+       * ziyi: support for security.
+
+2002-05-02  James Troup  <james@nocrew.org>
+
+       * jennifer (accept): call Katie.check_override() unconditional as
+       no-mail check moved into that function.
+       (do_byhand): likewise.
+
+       * katie.py (Katie.check_override): don't do anything if we're a)
+       not sending mail or b) the override disparity checks have been
+       disbled via Dinstall::OverrideDisparityCheck.
+
+       * jennifer (check_files): don't hard code Unstable as the suite
+       used to check for architecture validity; use
+       Dinstall::DefaultSuite instead, if it exists.
+       (accept): conditionalize
+
+       * katie.py (Katie.update_subst): support global maintainer
+       override with Dinstall::OverrideMaintainer.
+
+       * jennifer (check_distributions): new function, Distribution
+       validation and mapping.  Uses new SuiteMappings variable from
+       config file to abstract suite mappings.
+       (check_changes): call it.
+
+       * natalie: renamed; nothing imports or likely will for some time.
+
+       * denise (main): remove unused natalie import and init().
+
+       * natalie.py (init): removed.
+       (main): initalize here instead and don't hardcode the database
+       name.
+
+2002-04-30  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.close_bugs): new function, split out from
+       announce().
+       (Katie.announce): only call close_bugs() if Dinstall::CloseBugs is
+       true.
+       (Katie.close_bugs): new function, split out
+       (Katie.close_bugs): return immediately if there are no bugs to
+       close.
+
+       * jennifer (acknowledge_new): adapt for new utils.TemplateSubst().
+       * katie (do_reject): likewise.
+       (stable_install): likewise.
+       * katie.py (Katie.announce): likewise.
+       (Katie.accept): likewise.
+       (Katie.check_override): likewise.
+       (Katie.do_reject): likewise.
+       * lisa (do_bxa_notification): likewise.
+       * melanie (main): likewise.
+
+       * utils.py (TemplateSubst): change second argument to be a
+       filename rather than a template since every caller opened a file
+       on the fly which was ugly and leaked file descriptor.
+
+2002-04-29  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.announce): (modified) patch from Raphael Hertzog
+       <hertzog@debian.org> to send 'accepted' announce mails to the
+       PTS. [#128140]
+
+2002-04-24  James Troup  <james@nocrew.org>
+
+       * init_pool.sql (unstable_accepted): add two new fields to
+       unstable_accepted; in_accepted is a boolean indicating whether or
+       not the file is in accepted and last_used is a timestamp used by
+       rhona to determine when to remove symlinks for installed packages.
+
+       * katie.py (Katie.accept): auto-build support take 2.  Create
+       symlinks for all files into a seperate directory.  Add files to
+       unstable_accepted as paths to the new directory; mark them as
+       being in accepted for cameron.  Properly conditionalize it on a
+       configuration variable.
+
+       * katie (install): likewise.  Update symlinks to point into the
+       pool; mark the files for later deletion by rhona and mark them as
+       not being in accepted for cameron.
+
+       * rhona (clean_accepted_autobuild): new function.
+
+2002-04-22  James Troup  <james@nocrew.org>
+
+       * jennifer (check_files): handle db_access.get_location_id()
+       returning -1 properly/better.
+
+       * rhona (clean_fingerprints): new function.
+
+2002-04-21  James Troup  <james@nocrew.org>
+
+       * utils.py (touch_file): unused; remove.
+       (plural): likewise.
+
+       * jennifer (check_files): close file descriptor used to get the
+       control file.
+       (check_md5sums): likewise.
+       (callback): likewise.
+
+       * katie.py (Katie.do_reject): handle manual rejects much better;
+       call the editor first and get confirmation from the user before
+       proceeding.
+
+       * jennifer (check_signature): prefix_multi_line_string() moved to
+       utils.
+
+       * utils.py (prefix_multi_line_string): moved here from jennifer.
+
+2002-04-20  James Troup  <james@nocrew.org>
+
+       * lisa (main): handle non-existent files.
+
+       * ashley (main): allow *.katie files as arguments too.
+
+2002-04-19  James Troup  <james@nocrew.org>
+
+       * katie.py (Katie.accept): add stuff to help auto-building from
+       accepted; if the .orig.tar.gz is not part of the upload (i.e. it's
+       in the pool), create a symlink to it in the accepted directory and
+       add the .dsc and .{u,}deb(s) to a new 'unstable_accepted' table.
+
+       * katie (install): undo the "auto-building from accepted" stuff
+       (i.e. remove the .orig.tar.gz symlink and remove the files from
+       unstable_accepted table).
+
+2002-04-16  James Troup  <james@nocrew.org>
+
+       * jennifer (upload_too_new): fix typo which was causing all
+       timestamp comparisons to be against the .changes file.  Also move
+       back to the original directory so we do the comparisons against
+       accurate timestamps.
+
+       * tea (check_missing_tar_gz_in_dsc): new function.
+
+       * jennifer (check_dsc): add a check to ensure there is a .tar.gz
+       file mentioned in the .dsc.
+
+       * lisa (main): use X-Katie in the mail headers, not X-Lisa; that
+       way mails reach debian-{devel-,}changes@l.d.o.
+
+2002-04-02  Ryan Murray  <rmurray@debian.org>
+
+       * cron.daily: run shania after rhona
+       * cron.daily-non-US: likewise.
+
+2002-04-01  James Troup  <james@nocrew.org>
+
+       * katie: re-add proposed-updates/stable install support.
+
+       * katie.py (Katie.dump_vars): add changes["changes"] as an
+       optional field; should be mandatory later.
+
+2002-03-31  James Troup  <james@nocrew.org>
+
+       * katie (install): support a Suite::<foo>::CopyKatie similar to
+       CopyChanges.  Done seperately because .katie files don't need to
+       be mirrored and will probably be copied to another directory as a
+       result.
+
+       * halle (main): add missing debug to options.
+
+2002-03-29  James Troup  <james@nocrew.org>
+
+       * madison (main): add a -r/--regex option.
+
+2002-03-26  James Troup  <james@nocrew.org>
+
+       * lisa: don't trample on changes["distribution"]; make a copy of
+       it as changes["suite"] instead and use that.
+
+2002-03-16  Anthony Towns  <ajt@debian.org>
+
+       * templates/lisa.bxa_notification: Fix some grammatical errors.
+       Encourage contact via bxa@ftp-master email address.
+
+2002-03-15  James Troup  <james@nocrew.org>
+
+       * jennifer (check_timestamps): remove bogus raise in except.
+
+2002-03-15  Anthony Towns  <ajt@debian.org>
+
+       * cron.monthly: rotate mail/archive/bxamail as well as
+       mail/archive/mail. This is for a complete archive of
+       correspondence with the BXA.
+
+2002-03-14  Anthony Towns  <ajt@debian.org>
+
+       * crypto-in-main changes.
+
+       * utils.py (move, copy): add an optional perms= parameter to let you
+       set the resulting permissions of the moved/copied file
+       * katie.py (force_move): rejected/morgued files should be unreadable
+       * jennifer (do_byhand, acknowledge_new): pending new and byhand files
+       should be unreadable.
+
+2002-03-07  Ryan Murray  <rmurray@debian.org>
+
+       * katie (install): check for existance of "files id" key as well as
+       it being set to a valid value.
+       * katie (install): check for existense and valid value for location
+       id as well
+
+2002-03-05  Ryan Murray  <rmurray@debian.org>
+
+       * katie.py (do_reject): reread the reason file after editing it.
+
+2002-02-25  James Troup  <james@nocrew.org>
+
+       * jennifer (check_changes): don't enforce sanity in .changes file
+       names since it doesn't seem to be possible; pcmica-cs and similar
+       freak show packages in particular cause real problems.
+
+       * katie.py (Katie.check_dsc_against_db): initialize 'found' for
+       each dsc_file since the .orig.tar.gz checking code now uses it as
+       a boolean.  Fixes bizarro rejections which bogusly claimed
+       .diff.gz md5sum/size was incorrect.
+
+2002-02-24  James Troup  <james@nocrew.org>
+
+       * katie (process_it): reset reject_message.
+
+2002-02-22  James Troup  <james@nocrew.org>
+
+       * db_access.py(set_files_id): disable use of
+       currval('files_id_seq') because it was taking 3 seconds on auric
+       which is insane (most calls take < 0.01) and simply call
+       get_files_id() for the return value instead.
+
+       * katie.py (Katie.do_query): convenience function; unused by
+       default, useful for profiling.
+       * db_access.py (do_query): likewise.
+
+       * katie (install): fix insert SQL call when binary has no source.
+
+       * lisa (determine_new): auto-correct non-US/main to non-US.
+       (determine_new): add a warning when adding things to stable.
+       (edit_index): use our_raw_input().
+       (edit_overrides): likewise.
+       (do_new): likewise.  Use re.search() not re.match() since the
+       default answer may not be the first one.
+       (do_byhand): likewise.
+       (do_new): Default to 'S'kip and never 'A'dd.
+
+       * jennifer (action): pass prompt to our_raw_input().
+       * melanie (game_over): likewise.
+       * katie (action): likewise.
+
+       * utils.py (our_raw_input): add an optional prompt argument to
+       make the function more usable as a drop in replacement for
+       raw_input().
+
+       * jennifer (check_files): correct reject() to not double prefix
+       when using katie.py based functions.
+       (check_dsc): likewise.
+
+       * katie.py (Katie.reject): prepend a new line if appropriate
+       rathen than appending one to avoid double new lines when caller
+       adds one of his own.
+
+       * lisa (determine_new): warn if the package is also in other
+       components.
+
+2002-02-20  James Troup  <james@nocrew.org>
+
+       * jennifer (check_files): if .changes file lists "source" in
+       Architecture field, there must be a .dsc.
+
+2002-02-15  James Troup  <james@nocrew.org>
+
+       * ashley (main): add some missing fields.
+
+       * katie.py (Katie.check_dsc_against_db): fix to take into account
+       the fact that the .orig.tar.gz might be in byhand, accepted or
+       new.  Also fix calling of reject().
+       (Katie.check_binaries_against_db): fix calling of reject().
+       (Katie.check_source_against_db): likewise.
+       (Katie.dump_vars): add missing variables used for bug closures.
+
+       * lisa (changes_compare_by_time): sort by reverse time.
+
+       * katie.py (Katie.accept): log.
+       (Katie.dump_vars): missing has_key test for optional changes fields.
+
+       * jennifer (main): print "Accepted blah blah" to stdout, not stderr.
+       (process_it): traceback goes to stderr, not stdout.
+       (acknowledge_new): log.
+       (do_byhand): likewise.
+
+       * katie.py (Katie.update_subst): fix typo (Cnf vs. self.Cnf).
+
+       * add_constraints.sql: add grants for the new fingerprint table.
+
+2002-02-13  James Troup  <james@nocrew.org>
+
+       * katie (do_reject): basename the .changes filename before trying
+       to use it to construct the .reason filename.
+       (process_it): call Katie.update_subst() so do_reject() DTRT with
+       the mail template.
+       (do_reject): setup the mail template correctly.
+
+2002-02-12  James Troup  <james@nocrew.org>
+
+       * tea (process_dir): renamed 'arg' to 'unused' for clarity.
+       (check_files): don't abuse global dictionaries.
+       (Ent): use all variables.
+       (check_timestamps): don't abuse global dictionaries.
+
+       * fernanda.py: renamed to .py so lisa can import it.
+       (check_dsc): remove unused local variables (pychecker).
+       (display_changes): split off from check_changes.
+
+       * katie: rewritten; most of the functionality moves to jennifer;
+       what's left is the code to install packages once a day from the
+       'accepted' directory.
+
+       * jennifer: new program, processes packages in 'unchecked'
+       (i.e. most of the non-install functionality of old katie).
+
+       * katie.py: common functions shared between the clique of
+       jennifer, lisa and katie.
+
+       * lisa: new program; handles NEW and BYHAND packages.
+
+       * jeri (usage): new function.
+       (main): use it.
+       (check_package): remove unused local variable (pychecker).
+
+       * init_pool.sql: new table fingerprint.  Add fingerprint colums to
+       binaries and source.  Add install_date to source.
+
+       * halle (usage): new function.
+       (main): use it.  Remove unused options.
+       (check_changes): remove unused local variable (pychecker).
+
+       * add_constraints.sql: add fingerprint references.
+
+       * db_access.py (get_or_set_fingerprint_id): new function.
+
+       * ashley (main): new program; dumps the contents of a .katie file
+       to stdout.
+
+       * alyson (main): remove option handling since we don't actually
+       support any.
+       * cindy (main): likewise.
+
+       * remove unnecessary imports and pre-define globals (pychecker).
+
+2002-02-11  Anthony Towns  <ajt@debian.org>
+
+       * added installation-report and upgrade-report pseudo-packages
+
+2002-01-28  Martin Michlmayr  <tbm@cyrius.com>
+
+       * katie (update_subst): use Dinstall::TrackingServer.
+       * melanie (main): likewise.
+
+2002-01-27  James Troup  <james@nocrew.org>
+
+       * shania (main): it's IntLevel not IntVal; thanks to tbm@ for
+       noticing, jgg@ for fix.
+
+2002-01-19  James Troup  <james@nocrew.org>
+
+       * utils.py (extract_component_from_section): handle non-US
+       non-main properly.
+
+2002-01-12  James Troup  <james@nocrew.org>
+
+       * madison: add support for -S/--source-and-binary which displays
+       information for the source package and all it's binary children.
+
+2002-01-13  Anthony Towns  <ajt@debian.org>
+
+       * katie.conf: Remove Catherine Limit and bump stable to 2.2r5
+       * katie.conf: Add Dinstall::SigningKeyIds option, set to the 2001
+         and 2002 key ids.
+       * katie.conf-non-US: Likewise.
+       * ziyi: Suppoer Dinstall::SigningKeyIds to sign a Release file with
+         multiple keys automatically. This is probably only useful for
+         transitioning from an expired (or revoked?) key.
+
+2002-01-08  Ryan Murray  <rmurray@debian.org>
+
+       * debian/python-dep: new file that prints out python:Depends for
+         substvars
+       * debian/control: use python:Depends, build-depend on python
+         lower Depends: on postgresql to Suggests:
+       * debian/rules: determine python version, install to the correct
+         versioned dir
+
+2001-12-18  Anthony Towns  <ajt@debian.org>
+
+       * ziyi: unlink Release files before overwriting them (in case they've
+         been merged)
+       * ziyi: always include checksums/sizes for the uncompressed versions
+         of Sources and Packages, even if they're not present on disk
+
+2001-11-26  Ryan Murray  <rmurray@debian.org>
+
+       * ziyi (main): add SigningPubKey config option
+       * katie.conf: use SigningPubKey config option
+       * katie.conf-non-US: likewise
+
+2001-11-24  James Troup  <james@nocrew.org>
+
+       * katie (acknowledge_new): log newness.
+
+2001-11-24  Anthony Towns  <ajt@debian.org>
+
+       * ziyi (real_arch): bail out if some moron forgot to reset
+       untouchable on stable.
+       (real_arch): source Release files.
+
+2001-11-19  James Troup  <james@nocrew.org>
+
+       * claire.py (main): don't use apt_pkg.ReadConfigFileISC and
+       utils.get_conf().
+       * shania (main): likewise.
+
+       * rhona (main): add default options.
+
+       * db_access.py (get_archive_id): case independent.
+
+       * katie (action): sort files so that ordering is consistent
+       between mails; noticed/requested by Joey.
+
+2001-11-17  Ryan Murray  <rmurray@debian.org>
+
+       * utils.py: add get_conf function, change startup code to read all
+         config files to the Cnf that get_conf returns
+         use the component list from the katie conf rather than the hardcoded
+         list.
+       * all scripts: use new get_conf function
+       * shania: fix try/except around changes files
+       * jenna: only do debian-installer if it is a section in Cnf
+
+2001-11-16  Ryan Murray  <rmurray@debian.org>
+
+       * shania (main): Initialize days to a string of a number.
+                (main): Initialize Cnf vars before reading in Cnf
+
+2001-11-14  Ryan Murray  <rmurray@debian.org>
+
+       * shania (main): Initialize days to a number.
+
+2001-11-04  James Troup  <james@nocrew.org>
+
+       * docs/Makefile: use docbook-utils' docbook2man binary.
+
+       * Change all "if foo == []" constructs into "if not foo".
+
+       * katie (check_changes): when installing into stable from
+       proposed-updates, remove all non-stable target distributions.
+       (check_override): don't check for override disparities on stable
+       installs.
+       (stable_install): update install_bytes appropriately.
+       (reject): stable rejection support; i.e. don't remove files when
+       rejecting files in the pool, rather remove them from the
+       proposed-update suite instead, rhona will do the rest.
+       (manual_reject): support for a stable specific template.
+       (main): setup up stable rejector in subst.
+
+2001-11-04  Martin Michlmayr  <tbm@cyrius.com>
+
+       * debian/control (Build-Depends): docbook2man has been superseded
+       by docbook-utils.
+
+       * neve (main): exit with a more useful error message.
+       (update_suites): Suite::<suite>::Version, Origin and Description
+       are not required, so don't fail if they don't exist.
+
+       * db_access.py (get_archive_id): return -1 on error rather than
+       raise an exception.
+       (get_location_id): likewise.
+
+       * madison (main): don't exit on the first not-found package,
+       rather exit with an appropriate return code after processing all
+       packages.
+
+2001-11-03  James Troup  <james@nocrew.org>
+
+       * claire.py (find_dislocated_stable): add per-architecture
+       symlinks for dislocated architecture: all debs.
+
+2001-10-19  Anthony Towns  <ajt@debian.org>
+
+       * apt.conf*, katie.conf*: add mips, mipsel, s390 to testing.
+
+2001-10-10  Anthony Towns  <ajt@debian.org>
+
+       * claire.py (fix_component_section): do _not_ assign to None under
+       any circumstances
+
+2001-10-07  Martin Michlmayr  <tbm@cyrius.com>
+
+       * melanie (main): don't duplicate architectures when removing from
+       more than one suite.
+
+       * heidi (main, process_file, get_list): report suite name not ID.
+
+       * naima (nmu_p): be case insensitive.
+
+       * naima (action): more list handling clean ups.
+
+       * melanie (main): clean up lists handling to use string.join and
+       IN's.
+
+       * madison (main): clean up suite and architecture argument parsing
+       to use slices less and string.join more.
+
+       * utils.py (parse_changes): Use string.find() instead of slices for
+       string comparisons thereby avoid hardcoding the length of strings.
+       * ziyi (main): likewise.
+
+2001-10-07  James Troup  <james@nocrew.org>
+
+       * Remove mode argument from utils.open_files() calls if it's the
+       default, i.e. 'r'.
+
+2001-09-27  James Troup  <james@nocrew.org>
+
+       * katie (init): new function; options clean up.
+       (usage): add missing options, remove obsolete ones.
+       (main): adapt for the two changes above.  If the lock file or
+       new-ack log file don't exist, create them.  Don't try to open the
+       new-ack log file except running in new-ack mode.
+
+       * alyson (main): initialize all the tables that are based on the
+       conf file.
+
+       * utils.py (touch_file): like touch(1).
+       (where_am_i): typo.
+
+       * catherine (usage): new.
+       (main): use it.  options cleanup.
+       * claire.py: likewise.
+       * fernanda: likewise.
+       * heidi: likewise.
+       * jenna: likewise.
+       * shania: likewise.
+       * ziyi: likewise.
+
+       * andrea: options cleanup.
+       * charisma: likewise.
+       * julia: likewise.
+       * madison: likewise.
+       * melanie: likewise.
+       * natalie: likewise.
+       * rhona: likewise.
+       * tea: likewise.
+
+2001-09-26  James Troup  <james@nocrew.org>
+
+       * utils.py: default to sane config file locations
+       (/etc/katie/{apt,katie}.conf.  They can be the actual config files
+       or they can point to the real ones through use of a new Config
+       section.  Based on an old patch by Adam Heath.
+       (where_am_i): use the new default config stuff.
+       (which_conf_file): likewise.
+       (which_apt_conf_file): likewise.
+
+       * charisma (main): output defaults to
+       `Package~Version\tMaintainer'; input can be of either form.  When
+       parsing the new format do version comparisons, when parsing the
+       old format assume anything in the extra file wins.  This fixes the
+       problem of newer non-US packages being overwhelmed by older
+       versions still in stable on main.
+
+2001-09-17  James Troup  <james@nocrew.org>
+
+       * natalie.py (list): use result_join().
+
+       * denise (main): result_join() moved to utils.
+
+       * utils.py (result_join): move to utils; add an optional seperator
+       argument.
+
+2001-09-14  James Troup  <james@nocrew.org>
+
+       * heidi (set_suite): new function; does --set like natalie does,
+       i.e. turns it into a sequence of --add's and --remove's
+       internally.  This is a major win (~20 minute run time > ~20
+       seconds) in the common, everday (i.e. testing) case.
+       (get_id): common code used by set_suite() and process_file().
+       (process_file): call set_suite() and get_id().
+       (main): add logging support.
+
+       * julia: new script; syncs PostgeSQL with (LDAP-generated) passwd
+       files.
+
+       * utils.py (parse_changes): use slices or simple string comparison
+       in favour of regexes where possible.
+
+       * sql-aptvc.cpp (versioncmp): rewritten to take into account the
+       fact that the string VARDATA() points to are not null terminated.
+
+       * denise (result_join): new function; like string.join() but
+       handles None's.
+       (list): use it.
+       (main): likewise.
+
+       * charisma (main): python-pygresql 7.1 returns None not "".
+
+2001-09-14  Ryan Murray  <rmurray@debian.org>
+
+       * natalie.py (list): handle python-pygresql 7.1 returning None.
+
+2001-09-10  Martin Michlmayr  <tbm@cyrius.com>
+
+       * madison (main): return 1 if no package is found.
+
+2001-09-08  Martin Michlmayr  <tbm@cyrius.com>
+
+       * madison (main): better error handling for incorrect
+       -a/--architecture or -s/--suite arguments.
+       (usage): new.
+       (main): use it.
+
+2001-09-05  Ryan Murray  <rmurray@debian.org>
+
+       * charisma, madison, katie: remove use of ROUser
+       * katie.conf,katie.conf-non-US: remove defintion of ROUser
+
+2001-08-26  James Troup  <james@nocrew.org>
+
+       * katie (nmu_p.is_an_nmu): use maintaineremail to check for group
+       maintained packages at cjwatson@'s request.
+
+2001-08-21  James Troup  <james@nocrew.org>
+
+       * madison (main): add -a/--architecture support.
+
+       * jenna: use logging instead of being overly verbose on stdout.
+
+2001-08-11  Ryan Murray  <rmurray@debian.org>
+
+       * melanie: add functional help option
+
+2001-08-07  Anthony Towns  <ajt@debian.org>
+
+       * apt.conf, katie.conf: Add ia64 and hppa to testing.
+
+2001-07-28  James Troup  <james@nocrew.org>
+
+       * katie (check_dsc): ensure source version is >> than existing
+       source in target suite.
+
+2001-07-25  James Troup  <james@nocrew.org>
+
+       * natalie.py: add logging support.
+
+       * utils.py (open_file): make the second argument optional and
+       default to read-only.
+
+       * rene (main): work around broken source packages that duplicate
+       arch: all packages with arch: !all packages (no longer allowed
+       into the archive by katie).
+
+2001-07-13  James Troup  <james@nocrew.org>
+
+       * katie (action): don't assume distribution is a dictionary.
+       (update_subst): don't assume architecture is a dictionary and that
+       maintainer822 is defined.
+       (check_changes): recognise nk_format exceptions.
+       (check_changes): reject on 'testing' only uploads.
+       (check_files): when checking to ensure all packages are newer
+       versions check against arch-all packages too.
+       (check_dsc): enforce the existent of a sane set of mandatory
+       fields.  Ensure the version number in the .dsc (modulo epoch)
+       matches the version number in the .changes file.
+
+       * utils.py (changes_compare): ignore all exceptions when parsing
+       the changes files.
+       (build_file_list): don't UNDEF on a changes file with no format
+       field.
+
+2001-07-07  James Troup  <james@nocrew.org>
+
+       * katie (nmu_p.is_an_nmu): check 'changedby822' for emptiness
+       rather than 'changedbyname' to avoid false negatives on uploads
+       with an email-address-only Changed-By field.
+       (check_dsc): don't overwrite reject_message; append to it.
+       (check_signature): likewise.
+       (check_changes): likewise.
+       (announce): condition logging on 'action'.
+
+       * logging.py: new logging module.
+
+       * katie: Cleaned up code by putting Cnf["Dinstall::Options"]
+       sub-tree into a separate (global) variable.
+       (check_dsc): ensure format is 1.0 to retain backwards
+       compatability with dpkg-source in potato.
+       (main): only try to obtain the lock when not running in no-action
+       mode.
+       Use the new logging module.
+
+       * christina: initial version; only partially usable.
+
+2001-06-28  Anthony Towns  <ajt@debian.org>
+
+       * apt.conf: Add ExtraOverrides to auric.
+
+2001-06-25  James Troup  <james@nocrew.org>
+
+       * katie (nmu_p.is_an_nmu): the wonderful dpkg developers decided
+       they preferred the name 'Uploaders'.
+
+2001-06-23  James Troup  <james@nocrew.org>
+
+       * katie (check_files): fix typo in uncommon rejection message,
+       s/sourceversion/source version/.
+
+       * denise (main): we can't use print because stdout has been
+       redirected.
+
+       * katie (source_exists): new function; moved out of check_files()
+       and added support for binary-only NMUs of earlier sourceful NMUs.
+
+       * rhona (clean): find_next_free has moved.
+
+       * utils.py (find_next_free): new function; moved here from rhona.
+       Change too_many to be an argument with a default value, rather
+       than a hardcoded variable.
+
+       * shania: rewritten to work better; REJECTion reminder mail
+       handling got lost though.
+
+2001-06-22  James Troup  <james@nocrew.org>
+
+       * rhona (main): remove unused override code.
+
+       * fernanda (main): remove extraneous \n's from utils.warn calls.
+       * natalie.py (list): likewise.
+
+       * catherine, cindy, denise, heidi, jenna, katie, neve, rhona, tea:
+       use utils.{warn,fubar} where appropriate.
+
+2001-06-21  James Troup  <james@nocrew.org>
+
+       * katie (nmu_p): new class that encapsulates the "is a nmu?"
+       functionality.
+       (nmu_p.is_an_nmu): add support for multiple maintainers specified
+       by the "Maintainers" field in the .dsc file and maintainer groups.
+       (nmu_p.__init__): read in the list of group maintainer names.
+       (announce): use nmu_p.
+
+2001-06-20  James Troup  <james@nocrew.org>
+
+       * rene (main): hardcode the suite experimental is compared to by
+       name rather than number.
+
+       * katie (check_files): differentiate between doesn't-exist and
+       permission-denied in "can not read" rejections; requested by edd@.
+       (check_dsc): use os.path.exists rather than os.access to allow the
+       above check to kick in.
+
+       * heidi (process_file): read all input before doing anything and
+       use transactions.
+
+2001-06-15  James Troup  <james@nocrew.org>
+
+       * fernanda: new script; replaces old 'check' shell script
+       nastiness.
+
+2001-06-14  James Troup  <james@nocrew.org>
+
+       * katie: actually import traceback module to avoid amusing
+       infinite loop.
+
+2001-06-10  James Troup  <james@nocrew.org>
+
+       * utils.py (extract_component_from_section): fix to handle just
+       'non-free' and 'contrib'.  Also fix to handle non-US in a
+       completely case insensitive manner as a component.
+
+2001-06-08  James Troup  <james@nocrew.org>
+
+       * madison (arch_compare): sort function that sorts 'source' first
+       then alphabetically.
+       (main): use it.
+
+2001-06-05  Jeff Licquia  <jlicquia@progeny.com>
+
+       * catherine (poolize): explicitly make poolized_size a long so it
+       doesn't overflow when poolizing e.g. entire archives.
+
+2001-06-01  James Troup  <james@nocrew.org>
+
+       * utils.py (send_mail): throw exceptions rather than exiting.
+
+       * katie (process_it): catch exceptions and ignore them.
+
+2001-06-01  Michael Beattie  <mjb@debian.org>
+
+       * added update-mailingliststxt and update-readmenonus to update
+       those files, respectively. modified cron.daily{,-non-US} to
+       use them.
+
+2001-05-31  Anthony Towns  <ajt@debian.org>
+
+       * rhona: make StayOfExecution work.
+
+2001-05-31  James Troup  <james@nocrew.org>
+
+       * rhona (find_next_free): fixes to not overwrite files but rename
+       them by appending .<n> instead.
+       (clean): use find_next_free and use dated sub-directories in the
+       morgue.
+
+       * utils.py (move): don't overwrite files unless forced to.
+       (copy): likewise.
+
+2001-05-24  James Troup  <james@nocrew.org>
+
+       * katie (check_files): determine the source version here instead
+       of during install().
+       (check_files): check for existent source with bin-only NMU
+       support.
+       (main): sort the list of changes so that the source-must-exist
+       check Does The Right Thing(tm).
+
+       * utils.py (changes_compare): new function; sorts a list of
+       changes files by 'have-source', source, version.
+       (cc_fix_changes): helper function.
+       (parse_changes): use compiled regexes.
+       (fix_maintainer): likewise.
+
+       * rene (main): warn about packages in experimental that are
+       superseded by newer versions in unstable.
+
+2001-05-21  James Troup  <james@nocrew.org>
+
+       * rene (main): add support for checking for ANAIS (Architecture
+       Not Allowed In Source) packages.
+
+2001-05-17  James Troup  <james@nocrew.org>
+
+       * katie (check_changes): initalize `architecture' dictionary in
+       changes global so that if we can't parse the changes file for
+       whatever reason we don't undef later on.
+
+       * utils.py (parse_changes): fix handling of multi-line fields
+       where the first line did have data.
+
+2001-05-05  Anthony Towns  <ajt@debian.org>
+
+       * ziyi: Add "NotAutomatic: yes" to experimental Release files.
+       (It should always have been there. Ooopsy.)
+
+2001-05-03  Anthony Towns  <ajt@debian.org>
+
+       * jenna: Cleanup packages that move from arch:any to arch:all or
+       vice-versa.
+
+2001-04-24  Anthony Towns  <ajt@debian.org>
+
+       * ziyi: add ``SHA1:'' info to Release files. Also hack them up to
+       cope with debian-installer and boot-floppies' md5sum.txt.
+
+2001-04-16  James Troup  <james@nocrew.org>
+
+       * katie (check_changes): add missing %s format string argument.
+       (stable_install): temporary work around for morgue location to
+       move installed changes files into.
+       (stable_install): helps if you actually read in the template.
+       (manual_reject): fix for editing of reject messages which was
+       using the wrong variable name.
+
+       * jenna (generate_src_list): typo; s/package/source/; fixes undef crash.
+
+2001-04-13  James Troup  <james@nocrew.org>
+
+       * katie (manual_reject): Cc the installer.
+       (reject): don't.
+       (check_changes): remove unused maintainer-determination code.
+       (update_subst): add support for Changed-By when setting the
+       *MAINTAINER* variables.
+
+       * rene (bar): new function to check for packages on architectures
+       when they shouldn't be.
+
+       * natalie.py (main): use fubar() and warn() from utils.
+
+       * utils.py (whoami): new mini-function().
+       * melanie (main): use it.
+       * katie (manual_reject): likewise.
+
+2001-04-03  James Troup  <james@nocrew.org>
+
+       * katie (action): ignore exceptions from os.path.getmtime() so we
+       don't crash on non-existent changes files (e.g. when they are
+       moved between the start of the install run in cron.daily and the
+       time we get round to processing them).
+
+       * madison (main): also list source and accept -s/--suite.
+
+       * jenna (generate_src_list): missing \n in error message.
+
+       * katie (update_subst): add sane defaults for when changes is
+       skeletal.
+
+2001-03-29  James Troup  <james@nocrew.org>
+
+       * melanie (main): use fubar() and warn() from utils.  Remember who
+       the maintainer for the removed packages are and display that info
+       to the user.  Readd support for melanie-specific Bcc-ing that got
+       lost in the TemplateSubst transition.
+
+       * utils.py (fubar): new function.
+       (warn): like wise.
+
+       * db_access.py (get_maintainer): as below.
+
+       * charisma (get_maintainer): moved the bulk of this function to
+       db_access so that melanie can use it too.
+
+       * claire.py (find_dislocated_stable): restrict the override join
+       to those entries where the suite is stable; this avoids problems
+       with packages which have moved to new sections (e.g. science)
+       between stable and unstable.
+
+2001-03-24  James Troup  <james@nocrew.org>
+
+       * catherine (poolize): new function; not really independent of
+       main() fully, yet.
+       (main): use it.
+
+       * katie (stable_install): __SUITE__ needs to be space prefixed
+       because buildd's check for 'INSTALLED$'.
+
+2001-03-22  James Troup  <james@nocrew.org>
+
+       * utils.py (regex_safe): also need to escape '.'; noticed by ajt@.
+
+       * jenna: rewritten; now does deletions on a per-suite level
+       instead of a per-suite-component-architecture-type level.  This
+       allows mutli-component packages to be auto-cleaned (and as a
+       bonus, reduces the code size and duplication).
+
+2001-03-22  Anthony Towns  <ajt@debian.org>
+
+       * ziyi (main): fix ziyi to overwrite existing Release.gpg files
+       instead of just giving a gpg error.
+
+2001-03-21  James Troup  <james@nocrew.org>
+
+       * madison (main): use apt_pkg.VersionCompare to sort versions so
+       that output is correctly sorted for packages like debhlper.
+       Noticed by ajt@.
+
+       * tea (check_source_in_one_dir): actually find problematic source
+       packages.
+
+       * katie (check_dsc): remember the orig.tar.gz's location ID if
+       it's not in a legacy suite.
+       (check_diff): we don't use orig_tar_id.
+       (install): add code to handle sourceful diff-only upload of
+       packages which change components by copying the .orig.tar.gz into
+       the new component, if it doesn't already exist there.
+       (process_it): reset orig_tar_location (as above).
+
+       * melanie (main): use template substiution for the bug closing
+       emails.
+       (main): don't hardcode bugs.debian.org or packages.debian.org
+       either; use configuration items.
+
+       * katie: likewise.
+
+       * natalie.py (init): use None rather than 'localhost' for the
+       hostname given to pg.connect.
+
+       * utils.py (TemplateSubst): new function; lifted from
+       userdir-ldap.
+
+2001-03-21  Ryan Murray  <rmurray@debian.org>
+
+       * katie (announce): fix the case of non-existent
+       Suite::$SUITE::Announce.
+
+2001-03-20  Ryan Murray  <rmurray@debian.org>
+
+       * debian/rules (binary-indep): install melanie into /usr/bin/ not
+       /usr/.
+
+       * alyson (main): use config variable for database name.
+       * andrea (main): likewise.
+       * catherine (main): likewise.
+       * charisma (main): likewise.
+       * cindy (main): likewise.
+       * claire.py (main): likewise.
+       * denise (main): likewise.
+       * heidi (main): likewise.
+       * jenna (main): likewise.
+       * katie (main): likewise.
+       * madison (main): likewise.
+       * melanie (main): likewise.
+       * neve (main): likewise.
+       * rhona (main): likewise.
+       * tea (main): likewise.
+
+2001-03-15  James Troup  <james@nocrew.org>
+
+       * rhona (check_sources): fixed evil off by one (letter) error
+       which was causing only .dsc files to be deleted when cleaning
+       source packages.
+
+       * charisma (get_maintainer_from_source): remove really stupid and
+       gratuitous IN sub-query and replace with normal inner join.
+       (main): connect as read-only user nobody.
+
+       * rhona (clean_maintainers): rewritten to use SELECT and sub-query
+       with EXISTS.
+       (check_files): likewise; still disabled by default though.
+       (clean_binaries): add ' seconds' to the mysterious number in the
+       output.
+       (clean): likewise.
+
+       * tea (check_files): add missing global declaration on db_files.
+
+2001-03-14  James Troup  <james@nocrew.org>
+
+       * rhona: rewritten large chunks. Removed a lot of the silly
+       selecting into dictionaries and replaced it with 'where exists'
+       based sub queries.  Added support for StayOfExecution.  Fix the
+       problem with deleting dsc_files too early and disable cleaning of
+       unattached files.
+
+2001-03-14  Anthony Towns  <ajt@debian.org>
+
+       * katie (announce): also check Changed-By when trying to detect
+       NMUs.
+
+2001-03-06  Anthony Towns  <ajt@debian.org>
+
+       * ziyi (main): Generate Release.gpg files as well, using the key from
+       Dinstall::SigningKey in katie.conf, if present. That key has to be
+       passwordless, and hence kept fairly secretly.
+
+2001-03-02  James Troup  <james@nocrew.org>
+
+       * utils.py (str_isnum): new function; checks to see if the string
+       is a number.
+
+       * shania (main): fix _hideous_ bug which was causing all files > 2
+       weeks old to be deleted from incoming, even if they were part of a
+       valid upload.
+
+2001-02-27  James Troup  <james@nocrew.org>
+
+       * melanie (main): accept new argument -C/--carbon-copy which
+       allows arbitarty carbon-copying of the bug closure messages.
+       Cleaned up code by putting Cnf["Melanie::Options"] sub-tree into a
+       separate variable.
+
+2001-02-27  Anthony Towns  <ajt@debian.org>
+
+       * ziyi: new program; generates Release files.
+
+2001-02-25  James Troup  <james@nocrew.org>
+
+       * katie (reject): add missing '\n' to error message.
+       (manual_reject): likewise.
+       (install): catch exceptions from moving the changes file into DONE
+       and ignore them.
+
+       * tea (check_md5sums): new function.
+
+2001-02-25  Michael Beattie  <mjb@debian.org>
+
+       * melanie: use $EDITOR if available.
+
+2001-02-15  James Troup  <james@nocrew.org>
+
+       * utils.py (parse_changes): don't crash and burn on empty .changes
+       files.  Symptoms noticed by mjb@.
+
+2001-02-15  Adam Heath  <doogie@debian.org>
+
+       * denise (main): use an absolute path for the output filename.
+
+       * sql-aptvc.cpp: don't #include <utils/builtins.h> as it causes
+       compile errors with postgresql-dev >= 7.0.
+
+2001-02-12  James Troup  <james@nocrew.org>
+
+       * rene: initial version.
+
+       * andrea: initial version.
+
+       * catherine (main): remove obsolete assignment of arguments.
+
+2001-02-09  James Troup  <james@nocrew.org>
+
+       * catherine: first working version.
+
+2001-02-06  James Troup  <james@nocrew.org>
+
+       * katie (check_files): validate the priority field; i.e. ensure it
+       doesn't contain a '/' (to catch people prepending the priority
+       with the component rather than the section).
+       (check_override): don't warn about source packages; the only check
+       is on section and we have no GUI tools that would use the Section
+       field for a Sources file.
+       (announce): use tags rather than severities for NMUs.  Requested
+       by Josip Rodin <joy@>. [#78035]
+
+2001-02-04  James Troup  <james@nocrew.org>
+
+       * tea (check_override): new function; ensures packages in suites
+       are also in the override file.  Thanks to bod@ for noticing that
+       such packages existed.
+
+       * katie: move file type compiled regular expressions to utils as
+       catherine uses them too.
+       (check_changes): always default maintainer822 to the installer
+       address so that any bail out won't cause undefs later.
+       (check_files): update file type re's to match above.
+       (stable_install): likewise.
+       (reject): handle any except from moving the changes files.  Fixes
+       crashes on unreadable changes files.
+
+       * melanie (main): add an explanation of why things are not removed
+       from testing.
+
+2001-01-31  James Troup  <james@nocrew.org>
+
+       * melanie (main): ignore a) no message, b) removing from stable or
+       testing when invoked with -n/--no-action.
+
+       * katie (check_override): lower section before checking to see if
+       we're whining about 'non-US' versus 'non-US/main'.
+
+       * sql-aptvc.cpp: new file; wrapper around apt's version comparison
+       function so that we can use inside of PostgreSQL.
+
+2001-01-28  James Troup  <james@nocrew.org>
+
+       * katie: updated to pass new flag to parse_changes() and deal with
+       the exception raised on invalid .dsc's if appropriate.
+       * shania (main): likewise.
+       * melanie (main): likewise.
+
+       * tea (check_dscs): new function to validate all .dsc files in
+       unstable.
+
+       * utils.py (parse_changes): if passed an additional flag, validate
+       the .dsc file to ensure it's extractable by dpkg-source.
+       Requested by Ben Collins <bcollins@>.
+
+2001-01-27  James Troup  <james@nocrew.org>
+
+       * madison (main): connect to the DB as nobody.
+
+       * katie (check_files): remove support for -r/--no-version-check
+       since it makes no sense under katie (jenna will automatically
+       remove the (new) older version) and was evil in any event.
+       (check_changes): add missing new line to rejection message.
+       (check_dsc): likewise.
+       (process_it): reset reject_message here.
+       (main): not here.  Also remove support for -r.
+
+2001-01-26  James Troup  <james@nocrew.org>
+
+       * katie (check_override): don't whine about 'non-US/main' versus
+       'non-US'.
+
+2001-01-26  Michael Beattie  <mjb@debian.org>
+
+       * natalie.py (usage): new function.
+       (main): use it.
+
+2001-01-25  Antti-Juhani Kaijanaho  <gaia@iki.fi>
+
+       * update-mirrorlists: Update README.non-US too (request from Joy).
+
+2001-01-25  James Troup  <james@nocrew.org>
+
+       * katie (reject): catch any exception from utils.move() and just
+       pass, we previously only caught can't-overwrite errors and not
+       can't-read ones.
+
+       * jenna (generate_src_list): use ORDER BY in selects to avoid
+       unnecessary changes to Packages files.
+       (generate_bin_list): likewise.
+
+       * utils.py (extract_component_from_section): separated out from
+       build_file_list() as it's now used by claire too.
+
+       * claire.py (find_dislocated_stable): rewrite the query to extract
+       section information and handle component-less locations properly.
+       Thanks to ajt@ for the improved queries.
+       (fix_component_section): new function to fix components and
+       sections.
+
+2001-01-23  James Troup  <james@nocrew.org>
+
+       * katie (check_files): set file type for (u?)debs first thing, so
+       that if we continue, other functions which rely on file type
+       existing don't bomb out.  If apt_pkg or apt_inst raise an
+       exception when parsing the control file, don't try any other
+       checks, just drop out.
+       (check_changes): new test to ensure there is actually a target
+       distribution.
+
+2001-01-22  James Troup  <james@nocrew.org>
+
+       * katie (usage): s/dry-run/no-action/.  Noticed by Peter Gervai
+       <grin@>.
+       (check_changes): when mapping to unstable, remember to actually
+       add unstable to the suite list and not just remove the invalid
+       suite.
+
+2001-01-21  James Troup  <james@nocrew.org>
+
+       * katie (check_files): catch exceptions from debExtractControl()
+       and reject packages which raise any.
+
+2001-01-19  James Troup  <james@nocrew.org>
+
+       * katie (check_signature): basename() file name in rejection
+       message.
+
+2001-01-18  James Troup  <james@nocrew.org>
+
+       * katie (in_override_p): remember the section and priority from
+       the override file so we can check them against the package later.
+       (check_override): new function; checks section and priority (for
+       binaries) from the package against the override file and mails the
+       maintainer about any disparities.
+       (install): call check_override after announcing the upload.
+
+2001-01-16  James Troup  <james@nocrew.org>
+
+       * utils.py (build_file_list): catch ValueError's from splitting up
+       the files field and translate it into a parse error.
+
+       * tea: add support for finding unreferenced files.
+
+       * katie (in_override_p): add support for suite-aliasing so that
+       proposed-updates uploads work again.
+       (check_changes): catch parses errors from utils.build_file_list().
+       (check_dsc): likewise.
+       (check_diff): yet more dpkg breakage so we require even newer a
+       version.
+
+       * jenna (generate_bin_list): don't do nasty path munging that's no
+       longer needed.
+
+       * denise (main): support for non-US; and rename testing's override
+       files so they're based on testing's codename.
+
+2001-01-16  Martin Michlmayr  <tbm@cyrius.com>
+
+       * melanie: add to the bug closing message explaining what happens
+       (or rather doesn't) with bugs against packages that have been
+       removed.
+
+2001-01-14  James Troup  <james@nocrew.org>
+
+       * charisma (main): fix silly off-by-one error; suite priority
+       checking was done using "less than" rather than "less than or
+       equal to" which was causing weird hesienbugs with wrong Maintainer
+       fields.
+
+2001-01-10  James Troup  <james@nocrew.org>
+
+       * katie (in_override_p): adapted to use SQL-based overrides.
+       read_override_file function disappears.
+
+       * db_access.py: add new functions get_section_id, get_priority_id
+       and get_override_type_id.
+       (get_architecture_id): return -1 if the architecture is not found.
+
+       * heidi: switch %d -> %d in all SQL queries.
+       (get_list): Use string.join where appropriate.
+
+       * rhona (in_override_p): don't die if the override file doesn't
+       exist.
+       (main): warn if the override file doesn't exist.
+
+       * alyson: new script; will eventually sync the config file and the
+       SQL database.
+
+       * natalie.py: new script; manipulates overrides.
+
+       * melanie: new script; removes packages from suites.
+
+2001-01-08  James Troup  <james@nocrew.org>
+
+       * katie (re_bad_diff): whee; dpkg 1.8.1.1 didn't actually fix
+       anything it just changed the symptom.  Recognise the new breakage
+       and reject them too.
+
+2001-01-07  James Troup  <james@nocrew.org>
+
+       * katie (check_dsc): when adding the cwd copy of the .orig.tar.gz
+       to the .changes file, be sure to set up files[filename]["type"]
+       too.
+
+2001-01-06  James Troup  <james@nocrew.org>
+
+       * katie (check_diff): new function; detects bad diff files
+       produced by dpkg 1.8.1 and rejects thems.
+       (process_it): call check_diff().
+       (check_dsc): gar.  Add support for multiple versions of the
+       .orig.tar.gz file in the archive from -sa uploads.  Check md5sum
+       and size against all versions and use one which matches if
+       possible and exclude any that don't from being poolized to avoid
+       file overwrites.  Thanks to broonie@ for providing the example.
+       (install): skip any files marked as excluded as above.
+
+2001-01-05  James Troup  <james@nocrew.org>
+
+       * heidi (process_file): add missing argument to error message.
+
+2001-01-04  James Troup  <james@nocrew.org>
+
+       * heidi (main): fix handling of multiple files by reading all
+       files not just the first file n times (where n = the number of
+       files passed as arguments).
+
+2001-01-04  Anthony Towns  <ajt@debian.org>
+
+       * katie (check_dsc): proper fix for the code which locates the
+       .orig.tar.gz; check for '<filename>$' or '^<filename>$'.
+
+2000-12-20  James Troup  <james@nocrew.org>
+
+       * rhona: replace IN's with EXISTS's to make DELETE time for
+       binaries and source sane on auric.  Add a -n/--no-action flag and
+       make it stop actions if used.  Fixed a bug in binaries deletion
+       with no StayOfExecution (s/</<=/).  Add working -h/--help and
+       -V/--version.  Giving timing info on deletion till I'm sure it's
+       sane.
+
+       * katie (check_changes): map testing to unstable.
+
+       * madison: new script; shows versions in different architectures.
+
+       * katie (check_dsc): ensure size matches as well as md5sum;
+       suggested by Ben Collins <bcollins@debian.org> in Debian Bug
+       #69702.
+
+2000-12-19  James Troup  <james@nocrew.org>
+
+       * katie (reject): ignore the "can't overwrite file" exception from
+       utils.move() and leave the files where they are.
+       (reject): doh! os.access() test was reversed so we only tried to
+       move files which didn't exist... replaced with os.path.exists()
+       test the right way round.
+
+       * utils.py (move): raise an exception if we can't overwrite the
+       destination file.
+       (copy): likewise.
+
+2000-12-18  James Troup  <james@nocrew.org>
+
+       * rhona: first working version.
+
+       * db_access.py (get_files_id): force both sizes to be integers.
+
+       * katie (main): use size_type().
+
+       * utils.py (size_type): new function; pretty prints a file size.
+
+2000-12-17  James Troup  <james@nocrew.org>
+
+       * charisma (main): do version compares so that older packages do
+       not override newer ones and process source first as source wins
+       over binaries in terms of who we think of as the Maintainer.
+
+2000-12-15  James Troup  <james@nocrew.org>
+
+       * katie (install): use the files id for the .orig.tar.gz from
+       check_dsc().
+       (install): limit select for legacy source to a) source in legacy
+       or legacy-mixed type locations and b) distinct on files.id.
+       (install): rather than the bizarre insert new, delete old method
+       for moving legacy source into the pool, use a simple update of
+       files.
+       (process_it): initalize some globals before each process.
+
+2000-12-14  James Troup  <james@nocrew.org>
+
+       * katie (in_override_p): index on binary_type too since .udeb
+       overrides are in a different file.
+       (read_override_file): likewise.
+       (check_files): correct filename passed to get_files_id().
+       (check_dsc): we _have_ to preprend '/' to the filename to avoid
+       mismatches like jabber.orig.tar.gz versus libjabber.orig.tar.gz.
+       (check_dsc): remember the files id of the .orig.tar.gz, not the
+       location id.
+
+2000-12-13  James Troup  <james@nocrew.org>
+
+       * utils.py (poolify): force the component to lower case except for
+       non-US.
+
+       * katie (in_override_p): handle .udeb-specific override files.
+       (check_files): pass the binary type to in_override_p().
+       (check_dsc): remember the location id of the old .orig.tar.gz in
+       case it's not in the pool.
+       (install): use location id from dsc_files; which is where
+       check_dsc() puts it for old .orig.tar.gz files.
+       (install): install files after all DB work is complete.
+       (reject): basename() the changes filename.
+       (manual_reject): likewise.
+
+       * shania: new progam; replaces incomingcleaner.
+
+2000-12-05  James Troup  <james@nocrew.org>
+
+       * katie (check_changes): if inside stable and can't find files
+       from the .changes; assume it's installed in the pool and chdir()
+       to there.
+       (check_files): we are not installing for stable installs, so don't
+       check for overwriting existing files.
+       (check_dsc): likewise.
+       (check_dsc): reorder .orig.tar.gz handling so that we search in
+       the pool first and only then fall back on any .orig.tar.gz in the
+       cwd; this avoids false positives on the overwrite check when
+       people needlessly reupoad the .orig.tar.gz in a non-sa upload.
+       (install): if this is a stable install, bail out to
+       stable_install() immediately.
+       (install): dsc_files handling was horribly broken. a) we need to
+       add files from the .dsc and not the .changes (duh), b) we need to
+       add the .dsc file itself to dsc_files (to be consistent with neve
+       if for no other reason).
+       (stable_install): new function; handles installs from inside
+       proposed-updates to stable.
+       (acknowledge_new): basename changes_filename before doing
+       anything.
+       (process_it): absolutize the changes filename to avoid the
+       requirement of being in the same directory as the .changes file.
+       (process_it): save and restore the cwd as stable installs can
+       potentially jump into the pool to find files.
+
+       * jenna: dislocated_files support using claire.
+
+       * heidi (process_file): select package field from binaries
+       explicitly.
+
+       * db_access.py (get_files_id): fix cache key used.
+
+       * utils.py (build_file_list): fix 'non-US/non-free' case in
+       section/component splitting.
+       (move): use os.path.isdir() rather than stat.
+       (copy): likewise.
+
+       * claire.py: new file; stable in non-stable munger.
+
+       * tea: new file; simply ensures all files in the DB exist.
+
+2000-12-01  James Troup  <james@nocrew.org>
+
+       * katie (check_dsc): use regex_safe().
+       (check_changes): typo in changes{} key:
+       s/distributions/distribution/.
+       (install): use changes["source"], not files[file]["source"] as the
+       latter may not exist and the former is used elsewhere.  Commit the
+       SQL transaction earlier.
+
+       * utils.py (regex_safe): new function; escapes characters which
+       have meaning to SQL's regex comparison operator ('~').
+
+2000-11-30  James Troup  <james@nocrew.org>
+
+       * katie (install): pool_location is based on source package name,
+       not binary package.
+
+       * utils.py (move): if dest is a directory, append the filename
+       before chmod-ing.
+       (copy): ditto.
+
+       * katie (check_files): don't allow overwriting of existing .debs.
+       (check_dsc): don't allow overwriting of existing source files.
+
+2000-11-27  James Troup  <james@nocrew.org>
+
+       * katie (check_signature): don't try to load rsaref; it's
+       obsolete.
+       (in_override_p): don't try to lookup override entries for packages
+       with an invalid suite name.
+       (announce): don't assume the suite name is valid; use Find() to
+       lookup the mailing list name for announcements.
+
+       * utils.py (where_am_i): typo; hostname is in the first element,
+       not second.
+
+       * db_access.py (get_suite_id): return -1 on an unknown suite.
+
+2000-11-26  James Troup  <james@nocrew.org>
+
+       * katie (install): fix CopyChanges handling; typo in in checking
+       Cnf for CopyChanges flag and was calling non-existent function
+       copy_file.
+
+       * utils.py (copy): new function; clone of move without the
+       unlink().
+
+2000-11-25  James Troup  <james@nocrew.org>
+
+       * utils.py (build_file_list): handle non-US prefixes properly
+       (i.e. 'non-US' -> 'non-US/main' and 'non-US/libs' -> 'non-US/main'
+       + 'libs' not 'non-US/libs').
+       (send_mail): add '-odq' to sendmail invocation to avoid DNS lookup
+       delays.  This is possibly(/probably) exim speicifc and (like other
+       sendmail options) needs to be in the config file.
+
+2000-11-24  James Troup  <james@nocrew.org>
+
+       * rhona (check_sources): we need file id from dsc_files; not id.
+       Handle non .dsc source files being re-referenced in dsc_files.
+
+       * katie (in_override_p): strip out any 'non-US' prefix.
+       (check_files): use utils.where_am_i() rather than hardcoding.
+       (check_files): validate the component.
+       (install): use utils.where_am_i() rather than hardcoding.
+       (install): fix mail to go to actual recepient.
+       (reject): likewise.
+       (manual_reject): likewise.
+       (acknowledge_new): likewise.
+       (announce): likewise.
+
+       * db_access.py (get_component_id): ignore case when searching for
+       the component and don't crash if the component can't be found, but
+       return -1.
+       (get_location_id): handle -1 from get_component_id().
+
+       * jenna (generate_src_list): don't bring 'suite' into our big
+       multi-table-joining select as we already know the 'suite_id'.
+       (generate_bin_list): likewise.
+
+       * neve (main): don't quit if not on ftp-master.
+       (process_packages): remove unused variable 'suite_codename'.
+
+       * utils.py (move): actually move the file.
+       (build_file_list): handle non-US prefixes in the section.
+
+       * catherine (main): use which_conf_file().
+       * charisma (main): likewise.
+       * heidi (main): likewise.
+       * jenna (main): likewise.
+       * katie (main): likewise.
+       * neve (main): likewise.
+       * rhona (main): likewise.
+
+       * utils.py (where_am_i): new routine; determines the archive as
+       understood by other 'dak' programs.
+       (which_conf_file): new routine; determines the conf file to read.
+
+2000-11-17  James Troup  <james@nocrew.org>
+
+       * katie (install): fix where .changes files for proposed-updates
+       go.
+
+2000-11-04  James Troup  <james@nocrew.org>
+
+       * jenna (main): handle architecture properly if no
+       -a/--architecture argument is given, i.e. reset architecture with
+       the values for the suite for each suite.
+
+       * Add apt_pkg.init() to the start of all scripts as it's now
+       required by python-apt.
+
+2000-10-29  James Troup  <james@nocrew.org>
+
+       * jenna (generate_bin_list): take an additional argument 'type'
+       and use it in the SELECT.
+       (main): if processing component 'main', process udebs and debs.
+
+       * neve (process_packages): set up 'type' in 'binaries' (by
+       assuming .deb).
+
+       * katie (re_isadeb): accept ".udeb" or ".deb" as a file ending.
+       (check_files): set up files[file]["dbtype"].
+       (install): use files[file]["dbtype"] to set up the 'type' field in
+       the 'binaries' table.
+
+       * init_pool.sql: add a 'type' field to the 'binaries' table to
+       distinguisgh between ".udeb" and ".deb" files.
+
+       * utils.py (move): scrap basename() usage; use a "dir_p(dest) :
+       dest ? dirname(dest)" type check instead.
+
+       * katie (check_dsc): handle the case of an .orig.tar.gz not found
+       in the pool without crashing.  Also handle the case of being asked
+       to look for something other than an .orig.tar.gz in the pool.
+
+2000-10-26  James Troup  <james@nocrew.org>
+
+       * katie (install): fix filenames put into files table during
+       poolification of legacy source.
+
+       * utils.py (move): work around a bug in os.path.basename() which
+       cunningly returns '' if there is a trailing slash on the path
+       passed to it.
+
+       * katie (check_dsc): Remove more cruft.  If we find the
+       .orig.tar.gz in the pool and it's in a legacy (or legacy-mixed)
+       location, make a note of that so we can fix things in install().
+       (install): as above.  Move any old source out of legacy locations
+       so that 'apt-get source' will work.
+       (process_it): reset the flag that indicates to install that the
+       source needs moved.
+
+       * cron.daily: more.  Nowhere near complete yet though.
+
+       * katie (install): don't run os.makedirs, a) utils.move() does
+       this now, b) we weren't removing the user's umask and were
+       creating dirs with SNAFU permissions.
+       (check_dsc): rewrite the .orig.tar.gz handling to take into
+       account, err, package pools.  i.e. look anywhere in the pool
+       rather than faffing around with two simple paths.
+
+       * neve (process_sources): add the .dsc to dsc_files too.
+
+2000-10-25  James Troup  <james@nocrew.org>
+
+       * neve (process_sources): don't duplicate .orig.tar.gz's.
+
+2000-10-23  James Troup  <james@nocrew.org>
+
+       * utils.py (re_extract_src_version): moved here.
+
+       * neve: move re_extract_src_version to utils.
+       (process_packages): reflect change.
+
+       * katie (install): reflect change.
+
+2000-10-19  James Troup  <james@nocrew.org>
+
+       * jenna (generate_src_list): handle locations with null
+       components.
+       (generate_bin_list): likewise.
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..68c91be
--- /dev/null
+++ b/README
@@ -0,0 +1,37 @@
+***** WARNING ****** ***** WARNING ***** ****** WARNING ******
+
+This is unreleased alpha software; use it ENTIRELY at your own risk.
+
+***** WARNING ****** ***** WARNING ****** ***** WARNING ******
+
+dak is the collection of programs used to maintain the Debian
+project's archives.  It's not yet in a state where it can be easily
+used by others; if you want something to maintain a small archive and
+apt-ftparchive (from the apt-utils package) is insufficient, I strongly
+recommend you investigate mini-dinstall, debarchiver or similar.
+However, if you insist on trying to try using dak, please read the
+documentation in 'doc/README.first'.
+
+There are some manual pages and READMEs in the doc sub-directory.  The
+TODO file is an incomplete list of things needing to be done.
+
+There's a mailing list for discussion, development of and help with
+dak.  See:
+
+  http://lists.debian.org/debian-dak/
+
+for archives and details on how to subscribe.
+
+dak is developed and used on Linux but will probably work under any
+UNIX since it's almost entirely python and shell scripts.
+
+dak is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, a copy of which
+is provided under the name COPYING, or (at your option) any later
+version.
+
+-- 
+James Troup <james@nocrew.org>, Horsforth, Leeds
+Thu, 26 Dec 2002 16:56:02 +0000
+
diff --git a/config/debian-security/apt.conf b/config/debian-security/apt.conf
new file mode 100644 (file)
index 0000000..2df6072
--- /dev/null
@@ -0,0 +1,44 @@
+APT::FTPArchive::Contents off;
+
+Dir 
+{
+   ArchiveDir "/org/security.debian.org/ftp/";
+   OverrideDir "/org/security.debian.org/override/";
+   CacheDir "/org/security.debian.org/dak-database/";
+};
+
+Default
+{
+   Packages::Compress ". gzip bzip2";
+   Sources::Compress "gzip bzip2";
+   DeLinkLimit 0;
+   FileMode 0664;
+}
+
+tree "dists/stable/updates"
+{
+   FileList "/org/security.debian.org/dak-database/dists/stable_updates/$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/org/security.debian.org/dak-database/dists/stable_updates/$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm hppa i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.etch.$(SECTION)";
+   ExtraOverride "override.etch.extra.$(SECTION)";
+   SrcOverride "override.etch.$(SECTION).src";
+   Contents " ";
+   Packages::Compress "gzip bzip2";
+   Sources::Compress "gzip bzip2";
+};
+
+tree "dists/testing/updates"
+{
+   FileList "/org/security.debian.org/dak-database/dists/testing_updates/$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/org/security.debian.org/dak-database/dists/testing_updates/$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.lenny.$(SECTION)";
+   ExtraOverride "override.lenny.extra.$(SECTION)";
+   SrcOverride "override.lenny.$(SECTION).src";
+   Contents " ";
+   Packages::Compress "gzip bzip2";
+   Sources::Compress "gzip bzip2";
+};
diff --git a/config/debian-security/apt.conf.buildd b/config/debian-security/apt.conf.buildd
new file mode 100644 (file)
index 0000000..16154a6
--- /dev/null
@@ -0,0 +1,43 @@
+APT::FTPArchive::Contents off;
+
+Dir 
+{
+   ArchiveDir "/srv/security.debian.org/buildd/";
+   OverrideDir "/srv/security.debian.org/override/";
+   CacheDir "/srv/security.debian.org/dak-database/";
+};
+
+Default
+{
+   Packages::Compress ". gzip bzip2";
+   Sources::Compress ". gzip bzip2";
+   DeLinkLimit 0;
+   FileMode 0664;
+}
+
+bindirectory "etch"
+{
+   Packages "etch/Packages";
+   Sources "etch/Sources";
+   Contents " ";
+
+   BinOverride "override.etch.all3";
+   SrcOverride "override.etch.all3.src";
+   BinCacheDB "packages-accepted-etch.db";
+   PathPrefix "";
+   Packages::Extensions ".deb .udeb";
+};
+
+bindirectory "lenny"
+{
+   Packages "lenny/Packages";
+   Sources "lenny/Sources";
+   Contents " ";
+
+   BinOverride "override.lenny.all3";
+   SrcOverride "override.lenny.all3.src";
+   BinCacheDB "packages-accepted-lenny.db";
+   PathPrefix "";
+   Packages::Extensions ".deb .udeb";
+};
+
diff --git a/config/debian-security/cron.buildd b/config/debian-security/cron.buildd
new file mode 100755 (executable)
index 0000000..d38e093
--- /dev/null
@@ -0,0 +1,75 @@
+#! /bin/bash
+#
+# Executed after cron.unchecked
+
+ARCHS_stable="alpha amd64 arm hppa i386 ia64 mips mipsel powerpc sparc s390"
+ARCHS_testing="alpha amd64 armel hppa i386 ia64 mips mipsel powerpc sparc s390"
+DISTS="stable testing"
+SSH_SOCKET=~/.ssh/buildd.debian.org.socket
+
+set -e
+export SCRIPTVARS=/org/security.debian.org/dak/config/debian-security/vars
+. $SCRIPTVARS
+
+if [ -e $ftpdir/Archive_Maintenance_In_Progress ]; then
+       exit 0
+fi
+
+cd $masterdir
+for d in $DISTS; do
+       eval SOURCES_$d=`stat -c "%Y" $base/buildd/$d/Sources.gz`
+       eval PACKAGES_$d=`stat -c "%Y" $base/buildd/$d/Packages.gz`
+done
+
+apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate apt.conf.buildd
+dists=
+for d in $DISTS; do
+       eval NEW_SOURCES_$d=`stat -c "%Y" $base/buildd/$d/Sources.gz`
+       eval NEW_PACKAGES_$d=`stat -c "%Y" $base/buildd/$d/Packages.gz`
+       old=SOURCES_$d
+        new=NEW_$old
+        if [ ${!new} -gt ${!old} ]; then
+               if [ -z "$dists" ]; then
+                       dists="$d"
+               else
+                       dists="$dists $d"
+               fi
+               continue
+       fi
+       old=PACKAGES_$d
+       new=NEW_$old
+        if [ ${!new} -gt ${!old} ]; then
+               if [ -z "$dists" ]; then
+                       dists="$d"
+               else
+                       dists="$dists $d"
+               fi
+               continue
+       fi
+done
+
+if [ ! -z "$dists" ]; then
+       # setup ssh master process
+       ssh wbadm@buildd -S $SSH_SOCKET -MN 2> /dev/null &
+       SSH_PID=$!
+       while [ ! -S $SSH_SOCKET ]; do
+               sleep 1
+       done
+       trap 'kill -TERM $SSH_PID' 0
+       for d in $dists; do
+               archs=ARCHS_$d
+               ARCHS=${!archs}
+               cd /org/security.debian.org/buildd/$d
+               if [ "$d" != "oldstable" ]; then
+                       # disabled for oldstable-security by ajt 2008-01-01
+                       for a in $ARCHS; do
+                               quinn-diff -a /org/security.debian.org/buildd/Packages-arch-specific -A $a 2>/dev/null | ssh wbadm@buildd -S $SSH_SOCKET wanna-build -d $d-security -b $a/build-db --merge-partial-quinn
+
+                               ssh wbadm@buildd -S $SSH_SOCKET wanna-build -d $d-security -A $a -b $a/build-db --merge-packages < Packages
+                       done
+               else
+                       ssh buildd@bester.farm.ftbfs.de -i ~/.ssh/id_bester sleep 1
+               fi
+       done
+fi
+
diff --git a/config/debian-security/cron.daily b/config/debian-security/cron.daily
new file mode 100755 (executable)
index 0000000..4e02e57
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/sh
+#
+# Executed daily via cron, out of dak's crontab.
+
+set -e
+export SCRIPTVARS=/org/security.debian.org/dak/config/debian-security/vars
+. $SCRIPTVARS
+
+################################################################################
+
+# Fix overrides
+
+rsync --delete -r --include=override\* --exclude=\* --password-file /srv/non-us.debian.org/s3kr1t/rsync-password -ql security-master@ftp-master::indices/ $overridedir
+
+cd $overridedir
+for file in override*.gz; do
+       zcat $file > $(basename $file .gz)
+done
+find . -maxdepth 1 -mindepth 1 -type l | xargs --no-run-if-empty rm
+
+for suite in $suites; do
+    case $suite in
+               oldstable) override_suite=sarge;;
+               stable) override_suite=etch;;
+               testing) override_suite=lenny;;
+               *) echo "Unknown suite type ($suite)"; exit 1;;
+    esac
+    for component in $components; do
+               for override_type in $override_types; do
+                       case $override_type in
+                               deb) type="" ;;
+                               dsc) type=".src" ;;
+                               udeb) type=".debian-installer" ;;
+                       esac
+
+                       if [ "$override_type" = "udeb" ]; then
+                               if [ ! "$component" = "main" ]; then
+                                       continue
+                               fi
+                       fi
+                       zcat override.$override_suite.$component$type.gz | dak control-overrides -q -a -t $override_type -s $suite -c updates/$component
+               done
+    done
+done
+
+# Generate .all3 overides for the buildd support
+for dist in etch lenny; do
+    rm -f override.$dist.all3
+    components="main contrib non-free";
+    if [ -f override.$dist.main.debian-installer.gz ]; then
+               components="$components main.debian-installer"
+    fi
+    for component in $components; do
+               zcat override.$dist.$component.gz >> override.$dist.all3
+        if [ -e "override.$dist.$component.src.gz" ]; then
+                       zcat override.$dist.$component.src.gz >> override.$dist.all3.src
+               fi
+    done
+done
+
+################################################################################
+
+# Freshen Packages-Arch-Specific
+wget -qN http://buildd.debian.org/quinn-diff/Packages-arch-specific -O $base/buildd/Packages-arch-specific
+
+################################################################################
+
+cd $masterdir
+dak clean-queues
+dak clean-suites
+apt-ftparchive -q clean apt.conf
+apt-ftparchive -q clean apt.conf.buildd
+
+symlinks -d -r $ftpdir
+
+pg_dump obscurity > /org/security.debian.org/dak-backup/dump_$(date +%Y.%m.%d-%H:%M:%S)
+
+# Vacuum the database
+set +e
+echo "VACUUM; VACUUM ANALYZE;" | psql obscurity 2>&1 | egrep -v "^NOTICE:  Skipping \"pg_.*only table or database owner can VACUUM it$|^VACUUM$"
+set -e
+
+################################################################################
diff --git a/config/debian-security/cron.unchecked b/config/debian-security/cron.unchecked
new file mode 100755 (executable)
index 0000000..641f8bf
--- /dev/null
@@ -0,0 +1,36 @@
+#! /bin/sh
+
+set -e
+export SCRIPTVARS=/org/security.debian.org/dak/config/debian-security/vars
+. $SCRIPTVARS
+
+report=$queuedir/REPORT
+reportdis=$queuedir/REPORT.disembargo
+timestamp=$(date "+%Y-%m-%d %H:%M")
+doanything=false
+
+cd $unchecked
+changes=$(find . -maxdepth 1 -mindepth 1 -type f -name \*.changes | sed -e "s,./,," | xargs)
+if [ -n "$changes" ]; then
+  doanything=true
+  echo "$timestamp": "$changes"  >> $report
+  dak process-unchecked -a $changes >> $report
+  echo "--" >> $report
+fi
+
+cd $disembargo
+changes=$(find . -maxdepth 1 -mindepth 1 -type f -name \*.changes | sed -e "s,./,," | xargs)
+
+if [ -n "$changes" ]; then
+  doanything=true
+  echo "$timestamp": "$changes"  >> $reportdis
+  dak process-unchecked -a $changes >> $reportdis
+  echo "--" >> $reportdis
+fi
+
+if ! $doanything; then
+  echo "$timestamp": Nothing to do >> $report
+  exit 0
+fi
+
+sh $masterdir/cron.buildd
diff --git a/config/debian-security/cron.weekly b/config/debian-security/cron.weekly
new file mode 100755 (executable)
index 0000000..fc813ec
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Executed weekly via cron, out of dak's crontab.
+
+set -e
+export SCRIPTVARS=/org/security.debian.org/dak/config/debian-security/vars
+. $SCRIPTVARS
+
+################################################################################
+
+# Weekly generation of release files, then pushing mirrors.
+# Used as we have a "Valid-until" field in our release files of 10 days. In case
+# we dont have a security update in that time...
+cd $masterdir
+dak generate-releases
+sudo -u archvsync -H /home/archvsync/signal_security
+
+
+################################################################################
diff --git a/config/debian-security/dak.conf b/config/debian-security/dak.conf
new file mode 100644 (file)
index 0000000..3281f7e
--- /dev/null
@@ -0,0 +1,389 @@
+Dinstall
+{
+   GPGKeyring {
+     "/org/keyring.debian.org/keyrings/debian-keyring.gpg";
+     "/org/keyring.debian.org/keyrings/debian-keyring.pgp";
+   };
+   SigningKeyring "/org/non-us.debian.org/s3kr1t/dot-gnupg/secring.gpg";
+   SigningPubKeyring "/org/non-us.debian.org/s3kr1t/dot-gnupg/pubring.gpg";
+   SigningKeyIds "6070D3A1";
+   SendmailCommand "/usr/sbin/sendmail -odq -oi -t";
+   MyEmailAddress "Debian Installer <installer@ftp-master.debian.org>";
+   MyAdminAddress "ftpmaster@debian.org";
+   MyHost "debian.org";  // used for generating user@my_host addresses in e.g. manual_reject()
+   MyDistribution "Debian"; // Used in emails
+   BugServer "bugs.debian.org";
+   PackagesServer "packages.debian.org";
+   LockFile "/org/security.debian.org/dak/lock";
+   Bcc "archive@ftp-master.debian.org";
+   // GroupOverrideFilename "override.group-maint";
+   FutureTimeTravelGrace 28800; // 8 hours
+   PastCutoffYear "1984";
+   SkipTime 300;
+   CloseBugs "false";
+   OverrideDisparityCheck "false";
+   BXANotify "false";
+   QueueBuildSuites
+   {
+     stable;
+     testing;
+   };
+   SecurityQueueHandling "true";     
+   SecurityQueueBuild "true";     
+   DefaultSuite "stable";
+   SuiteSuffix "updates";
+   OverrideMaintainer "dak@security.debian.org";
+   StableDislocationSupport "false";
+   LegacyStableHasNoSections "false";
+};
+
+Process-New
+{
+  AcceptedLockFile "/org/security.debian.org/lock/unchecked.lock";
+};
+
+Import-Users-From-Passwd
+{
+  ValidGID "800";
+  // Comma separated list of users who are in Postgres but not the passwd file
+  KnownPostgres "postgres,dak,www-data,udmsearch";
+};
+
+Queue-Report
+{
+  Directories
+  {
+    // byhand;
+    // new;
+    unembargoed;
+  };
+};
+
+Clean-Queues
+{
+  Options
+  {
+    Days 14;
+  };
+ MorgueSubDir "queue";
+};
+
+Rm
+{
+  Options
+  {
+    Suite "unstable";
+  };
+
+  MyEmailAddress "Debian Archive Maintenance <ftpmaster@ftp-master.debian.org>";
+  LogFile "/org/security.debian.org/dak-log/removals.txt";
+};
+
+Init-Archive
+{
+  ExportDir "/org/security.debian.org/dak/import-archive-files/";
+};
+
+Clean-Suites
+{
+  // How long (in seconds) dead packages are left before being killed
+  StayOfExecution 129600; // 1.5 days
+  QueueBuildStayOfExecution 86400; // 24 hours
+  MorgueSubDir "pool";
+  OverrideFilename "override.source-only";
+};
+
+Security-Install
+{
+  ComponentMappings
+  {
+    main "ftp-master.debian.org:/pub/UploadQueue";
+    contrib "ftp-master.debian.org:/pub/UploadQueue";
+    non-free "ftp-master.debian.org:/pub/UploadQueue";
+    non-US/main "non-us.debian.org:/pub/UploadQueue";
+    non-US/contrib "non-us.debian.org:/pub/UploadQueue";
+    non-US/non-free "non-us.debian.org:/pub/UploadQueue";
+  };
+};
+
+Suite
+{
+  // Priority determines which suite is used for the Maintainers file
+  // as generated by 'dak make-maintainers' (highest wins).
+
+  Stable
+  {
+       Components 
+       {
+         updates/main;
+         updates/contrib;
+         updates/non-free;
+       };
+       Architectures 
+       {
+         source;  
+         all;
+         amd64; 
+         alpha; 
+         arm;
+         hppa;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "dak@security.debian.org";
+       Version "";
+       Origin "Debian";
+       Label "Debian-Security";
+       Description "Debian 4.0 Security Updates";
+       ValidTime 864000; // 10 days
+       CodeName "etch";
+       OverrideCodeName "etch";
+       CopyDotDak "/org/security.debian.org/queue/done/";
+  };
+
+  Testing
+  {
+       Components 
+       {
+         updates/main;
+         updates/contrib;
+         updates/non-free;
+       };
+       Architectures 
+       {
+         source;  
+         all;
+         amd64; 
+         alpha; 
+         arm;
+         armel;
+         hppa;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "dak@security.debian.org";
+       Version "";
+       Origin "Debian";
+       Label "Debian-Security";
+       Description "Debian testing Security Updates";
+       ValidTime 864000; // 10 days
+       CodeName "lenny";
+       OverrideCodeName "lenny";
+       CopyDotDak "/org/security.debian.org/queue/done/";
+  };
+};
+
+SuiteMappings
+{
+ "silent-map stable-security stable";
+ // JT - FIXME, hackorama
+ // "silent-map testing-security stable";
+  "silent-map etch-secure stable";
+  "silent-map testing-security testing";
+};
+
+Dir
+{
+  Root "/org/security.debian.org/ftp/";
+  Pool "/org/security.debian.org/ftp/pool/";
+  Dak "/org/security.debian.org/dak/";
+  Templates "/org/security.debian.org/dak/templates/";
+  PoolRoot "pool/";
+  Override "/org/security.debian.org/override/";
+  Lock "/org/security.debian.org/lock/";
+  Lists "/org/security.debian.org/dak-database/dists/";
+  Log "/org/security.debian.org/dak-log/";
+  Morgue "/org/security.debian.org/morgue/";
+  MorgueReject "reject";
+  Override "/org/security.debian.org/scripts/override/";
+  QueueBuild "/org/security.debian.org/buildd/";
+  Upload "/srv/queued/UploadQueue/";
+  Queue
+  {
+    Accepted "/org/security.debian.org/queue/accepted/";
+    Byhand "/org/security.debian.org/queue/byhand/";
+    Done "/org/security.debian.org/queue/done/";
+    Holding "/org/security.debian.org/queue/holding/";
+    New "/org/security.debian.org/queue/new/";
+    Reject "/org/security.debian.org/queue/reject/";
+    Unchecked "/org/security.debian.org/queue/unchecked/";
+    ProposedUpdates "/does/not/exist/"; // XXX fixme
+    OldProposedUpdates "/does/not/exist/"; // XXX fixme
+
+    Embargoed "/org/security.debian.org/queue/embargoed/";
+    Unembargoed "/org/security.debian.org/queue/unembargoed/";
+    Disembargo "/org/security.debian.org/queue/unchecked-disembargo/";
+  };
+};
+
+DB
+{
+  Name "obscurity";
+  Host ""; 
+  Port -1;
+
+};
+
+Architectures
+{
+
+  source "Source";
+  all "Architecture Independent";
+  alpha "DEC Alpha";
+  hppa "HP PA RISC";
+  arm "ARM";
+  armel "ARM EABI";
+  i386 "Intel ia32";
+  ia64 "Intel ia64";
+  mips "MIPS (Big Endian)";
+  mipsel "MIPS (Little Endian)";
+  powerpc "PowerPC";
+  s390 "IBM S/390";
+  sparc "Sun SPARC/UltraSPARC";
+  amd64 "AMD x86_64 (AMD64)";
+
+};
+
+Archive
+{
+
+  security
+  {
+    OriginServer "security.debian.org";
+    PrimaryMirror "security.debian.org";
+    Description "Security Updates for the Debian project";
+  };
+
+};
+
+Component
+{
+
+  updates/main
+  {
+       Description "Main (updates)";
+       MeetsDFSG "true";
+  };
+
+  updates/contrib
+  {
+       Description "Contrib (updates)";
+       MeetsDFSG "true";
+  };
+
+  updates/non-free
+  {
+       Description "Software that fails to meet the DFSG";
+       MeetsDFSG "false";
+  };
+
+};
+
+ComponentMappings
+{
+ "main updates/main";
+ "contrib updates/contrib";
+ "non-free updates/non-free";
+ "non-US/main updates/main";
+ "non-US/contrib updates/contrib";
+ "non-US/non-free updates/non-free";
+};
+
+Section
+{
+  admin;
+  base;
+  comm;
+  debian-installer;
+  devel;
+  doc;
+  editors;
+  electronics;
+  embedded;
+  games;
+  gnome;
+  graphics;
+  hamradio;
+  interpreters;
+  kde;
+  libdevel;
+  libs;
+  mail;
+  math;
+  misc;
+  net;
+  news;
+  oldlibs;
+  otherosfs;
+  perl;
+  python;
+  science;
+  shells;
+  sound;
+  tex;
+  text;
+  utils;
+  web;
+  x11;
+  non-US;
+};
+
+Priority
+{
+  required 1;
+  important 2;
+  standard 3;
+  optional 4;
+  extra 5;
+  source 0; // i.e. unused
+};
+
+OverrideType
+{
+  deb;
+  udeb;
+  dsc;
+};
+
+Location
+{
+  /org/security.debian.org/ftp/dists/
+    {
+      Archive "security";
+      Type "legacy";
+    };
+
+  /org/security.debian.org/ftp/pool/
+    {
+      Archive "security";
+      Suites 
+       {
+         Stable;
+         Testing;
+        };
+      Type "pool";
+    };
+};
+
+Urgency
+{
+  Default "low";
+  Valid
+  {
+    low;
+    medium;
+    high;
+    emergency;
+    critical;
+  };
+};
diff --git a/config/debian-security/vars b/config/debian-security/vars
new file mode 100644 (file)
index 0000000..e475745
--- /dev/null
@@ -0,0 +1,22 @@
+# locations used by many scripts
+
+base=/org/security.debian.org
+ftpdir=$base/ftp/
+masterdir=$base/dak/config/debian-security/
+overridedir=$base/override
+queuedir=$base/queue/
+unchecked=$queuedir/unchecked/
+disembargo=$queuedir/unchecked-disembargo/
+accepted=$queuedir/accepted/
+done=$queuedir/done/
+
+uploadhost=ftp-master.debian.org
+uploaddir=/pub/UploadQueue/
+
+components="main non-free contrib"
+suites="stable testing"
+override_types="deb dsc udeb"
+
+PATH=$masterdir:$PATH
+umask 022
+
diff --git a/config/debian/Contents.top b/config/debian/Contents.top
new file mode 100644 (file)
index 0000000..1fd5918
--- /dev/null
@@ -0,0 +1,32 @@
+This file maps each file available in the Debian GNU/Linux system to
+the package from which it originates.  It includes packages from the
+DIST distribution for the ARCH architecture.
+
+You can use this list to determine which package contains a specific
+file, or whether or not a specific file is available.  The list is
+updated weekly, each architecture on a different day.
+
+When a file is contained in more than one package, all packages are
+listed.  When a directory is contained in more than one package, only
+the first is listed.
+
+The best way to search quickly for a file is with the Unix `grep'
+utility, as in `grep <regular expression> CONTENTS':
+
+ $ grep nose Contents
+ etc/nosendfile                                          net/sendfile
+ usr/X11R6/bin/noseguy                                   x11/xscreensaver
+ usr/X11R6/man/man1/noseguy.1x.gz                        x11/xscreensaver
+ usr/doc/examples/ucbmpeg/mpeg_encode/nosearch.param     graphics/ucbmpeg
+ usr/lib/cfengine/bin/noseyparker                        admin/cfengine
+
+This list contains files in all packages, even though not all of the
+packages are installed on an actual system at once.  If you want to
+find out which packages on an installed Debian system provide a
+particular file, you can use `dpkg --search <filename>':
+
+ $ dpkg --search /usr/bin/dselect
+ dpkg: /usr/bin/dselect
+
+
+FILE                                                    LOCATION
diff --git a/config/debian/apt.conf b/config/debian/apt.conf
new file mode 100644 (file)
index 0000000..ff821f6
--- /dev/null
@@ -0,0 +1,214 @@
+Dir 
+{
+   ArchiveDir "/srv/ftp.debian.org/ftp/";
+   OverrideDir "/srv/ftp.debian.org/scripts/override/";
+   CacheDir "/srv/ftp.debian.org/database/";
+};
+
+Default
+{
+   Packages::Compress "gzip bzip2";
+   Sources::Compress "gzip bzip2";
+   Contents::Compress "gzip";
+   DeLinkLimit 0;
+   MaxContentsChange 25000;
+   FileMode 0664;
+}
+
+TreeDefault
+{
+   Contents::Header "/srv/ftp.debian.org/dak/config/debian/Contents.top";
+};
+
+tree "dists/proposed-updates"
+{
+   FileList "/srv/ftp.debian.org/database/dists/proposed-updates_$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/srv/ftp.debian.org/database/dists/proposed-updates_$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm hppa i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.etch.$(SECTION)";
+   ExtraOverride "override.etch.extra.$(SECTION)";
+   SrcOverride "override.etch.$(SECTION).src";
+   Contents " ";
+};
+
+tree "dists/testing"
+{
+   FakeDI "dists/unstable";
+   FileList "/srv/ftp.debian.org/database/dists/testing_$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/srv/ftp.debian.org/database/dists/testing_$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.lenny.$(SECTION)";
+   ExtraOverride "override.lenny.extra.$(SECTION)";
+   SrcOverride "override.lenny.$(SECTION).src";
+};
+
+tree "dists/testing-proposed-updates"
+{
+   FileList "/srv/ftp.debian.org/database/dists/testing-proposed-updates_$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/srv/ftp.debian.org/database/dists/testing-proposed-updates_$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.lenny.$(SECTION)";
+   ExtraOverride "override.lenny.extra.$(SECTION)";
+   SrcOverride "override.lenny.$(SECTION).src";
+   Contents " ";
+};
+
+tree "dists/unstable"
+{
+   FileList "/srv/ftp.debian.org/database/dists/unstable_$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/srv/ftp.debian.org/database/dists/unstable_$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm armel hppa hurd-i386 i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.sid.$(SECTION)";
+   ExtraOverride "override.sid.extra.$(SECTION)";
+   SrcOverride "override.sid.$(SECTION).src";
+};
+
+// debian-installer
+
+tree "dists/proposed-updates/main"
+{
+   FileList "/srv/ftp.debian.org/database/dists/proposed-updates_main_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm hppa i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.etch.main.$(SECTION)";
+   SrcOverride "override.etch.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents " ";
+};
+
+tree "dists/testing/main"
+{
+   FileList "/srv/ftp.debian.org/database/dists/testing_main_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.lenny.main.$(SECTION)";
+   SrcOverride "override.lenny.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb";
+};
+
+tree "dists/testing/non-free"
+{
+   FileList "/srv/ftp.debian.org/database/dists/testing_non-free_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.lenny.main.$(SECTION)";
+   SrcOverride "override.lenny.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb-nf";
+};
+
+tree "dists/testing-proposed-updates/main"
+{
+   FileList "/srv/ftp.debian.org/database/dists/testing-proposed-updates_main_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.lenny.main.$(SECTION)";
+   SrcOverride "override.lenny.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents " ";
+};
+
+tree "dists/unstable/main"
+{
+   FileList "/srv/ftp.debian.org/database/dists/unstable_main_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm armel hppa hurd-i386 i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.sid.main.$(SECTION)";
+   SrcOverride "override.sid.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb";
+};
+
+tree "dists/unstable/non-free"
+{
+   FileList "/srv/ftp.debian.org/database/dists/unstable_non-free_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm armel hppa hurd-i386 i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.sid.main.$(SECTION)";
+   SrcOverride "override.sid.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb-nf";
+};
+
+tree "dists/experimental/main"
+{
+   FileList "/srv/ftp.debian.org/database/dists/experimental_main_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.sid.main.$(SECTION)";
+   SrcOverride "override.sid.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb";
+};
+
+tree "dists/experimental/non-free"
+{
+   FileList "/srv/ftp.debian.org/database/dists/experimental_non-free_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm armel hppa hurd-i386 i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.sid.main.$(SECTION)";
+   SrcOverride "override.sid.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb-nf";
+};
+
+// Experimental
+
+tree "dists/experimental"
+{
+   FileList "/srv/ftp.debian.org/database/dists/experimental_$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/srv/ftp.debian.org/database/dists/experimental_$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm armel hppa hurd-i386 i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.sid.$(SECTION)";
+   SrcOverride "override.sid.$(SECTION).src";
+};
+
+tree "dists/etch-m68k"
+{
+   FakeDI "dists/unstable";
+   FileList "/srv/ftp.debian.org/database/dists/etch-m68k_$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/srv/ftp.debian.org/database/dists/etch-m68k_$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "m68k source";
+   BinOverride "override.etch.$(SECTION)";
+   ExtraOverride "override.etch.extra.$(SECTION)";
+   SrcOverride "override.etch.$(SECTION).src";
+};
+
+tree "dists/etch-m68k/main"
+{
+   FileList "/srv/ftp.debian.org/database/dists/etch-m68k_main_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "m68k";
+   BinOverride "override.etch.main.$(SECTION)";
+   SrcOverride "override.etch.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb";
+};
+
+tree "dists/etch-m68k/non-free"
+{
+   FileList "/srv/ftp.debian.org/database/dists/etch-m68k_non-free_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "m68k";
+   BinOverride "override.etch.main.$(SECTION)";
+   SrcOverride "override.etch.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb-nf";
+};
diff --git a/config/debian/apt.conf.buildd b/config/debian/apt.conf.buildd
new file mode 100644 (file)
index 0000000..65a8363
--- /dev/null
@@ -0,0 +1,37 @@
+Dir 
+{
+   ArchiveDir "/srv/incoming.debian.org/buildd/";
+   OverrideDir "/srv/ftp.debian.org/scripts/override/";
+   CacheDir "/srv/ftp.debian.org/database/";
+};
+
+Default
+{
+   Packages::Compress ". bzip2 gzip";
+   Sources::Compress ". bzip2 gzip";
+   DeLinkLimit 0;
+   FileMode 0664;
+}
+
+bindirectory "incoming"
+{
+   Packages "Packages";
+   Contents " ";
+
+   BinOverride "override.sid.all3";
+   BinCacheDB "packages-accepted.db";
+   
+   FileList "/srv/ftp.debian.org/database/dists/unstable_accepted.list";
+
+   PathPrefix "";
+   Packages::Extensions ".deb .udeb";
+};
+
+bindirectory "incoming/"
+{
+   Sources "Sources";
+   BinOverride "override.sid.all3";
+   SrcOverride "override.sid.all3.src";
+   FileList "/srv/ftp.debian.org/database/dists/unstable_accepted.list";
+};
+
diff --git a/config/debian/apt.conf.stable b/config/debian/apt.conf.stable
new file mode 100644 (file)
index 0000000..c75b5f0
--- /dev/null
@@ -0,0 +1,58 @@
+Dir 
+{
+   ArchiveDir "/srv/ftp.debian.org/ftp/";
+   OverrideDir "/srv/ftp.debian.org/scripts/override/";
+   CacheDir "/srv/ftp.debian.org/database/";
+};
+
+Default
+{
+   Packages::Compress "gzip bzip2";
+   Sources::Compress "gzip bzip2";
+   Contents::Compress "gzip";
+   DeLinkLimit 0;
+   FileMode 0664;
+}
+
+TreeDefault
+{
+   Contents::Header "/srv/ftp.debian.org/dak/config/debian/Contents.top";
+};
+
+tree "dists/stable"
+{
+   FileList "/srv/ftp.debian.org/database/dists/stable_$(SECTION)_binary-$(ARCH).list";
+   SourceFileList "/srv/ftp.debian.org/database/dists/stable_$(SECTION)_source.list";
+   Sections "main contrib non-free";
+   Architectures "alpha amd64 arm hppa i386 ia64 mips mipsel powerpc s390 sparc source";
+   BinOverride "override.etch.$(SECTION)";
+   ExtraOverride "override.etch.extra.$(SECTION)";
+   SrcOverride "override.etch.$(SECTION).src";
+};
+
+// debian-installer
+
+tree "dists/stable/main"
+{
+   FileList "/srv/ftp.debian.org/database/dists/stable_main_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm hppa i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.etch.main.$(SECTION)";
+   SrcOverride "override.etch.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb";
+};
+
+tree "dists/stable/non-free"
+{
+   FileList "/srv/ftp.debian.org/database/dists/stable_non-free_$(SECTION)_binary-$(ARCH).list";
+   Sections "debian-installer";
+   Architectures "alpha amd64 arm hppa i386 ia64 mips mipsel powerpc s390 sparc";
+   BinOverride "override.etch.main.$(SECTION)";
+   SrcOverride "override.etch.main.src";
+   BinCacheDB "packages-debian-installer-$(ARCH).db";
+   Packages::Extensions ".udeb";
+   Contents "$(DIST)/../Contents-udeb-nf";
+};
+
diff --git a/config/debian/cron.buildd b/config/debian/cron.buildd
new file mode 100755 (executable)
index 0000000..d7a887d
--- /dev/null
@@ -0,0 +1,6 @@
+#! /bin/sh
+#
+# Called from cron.unchecked to update wanna-build, each time it runs.
+#
+ssh -o BatchMode=yes -o ConnectTimeout=30 -o SetupTimeout=240 wbadm@buildd /org/wanna-build/trigger.often
+exit 0
diff --git a/config/debian/cron.dinstall b/config/debian/cron.dinstall
new file mode 100755 (executable)
index 0000000..fcbb149
--- /dev/null
@@ -0,0 +1,344 @@
+#! /bin/sh
+#
+# Executed daily via cron, out of dak's crontab.
+
+set -e
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+
+################################################################################
+
+# Start logging
+NOW=`date "+%Y.%m.%d-%H:%M:%S"`
+LOGFILE="$logdir/dinstall_${NOW}.log"
+exec > "$LOGFILE" 2>&1
+
+ts() {
+       TS=$(($TS+1));
+       echo "Archive maintenance timestamp $TS ($1): $(date +%H:%M:%S)"
+}
+
+TS=-1
+ts "startup"
+
+NOTICE="$ftpdir/Archive_Maintenance_In_Progress"
+LOCKCU="$lockdir/daily.lock"
+LOCKAC="$lockdir/unchecked.lock"
+BRITNEYLOCK="$lockdir/britney.lock"
+lockac=0
+
+cleanup() {
+  rm -f "$NOTICE"
+  rm -f "$LOCKCU"
+  if [ "$lockac" -eq "1" ]; then
+    rm -f "$LOCKAC"
+  fi
+  echo "Cleanup"
+}
+lockfile -l 3600 $LOCKCU
+trap cleanup 0
+
+# This file is simply used to indicate to britney whether or not
+# the Packages file updates completed sucessfully.  It's not a lock
+# from our point of view
+touch ${BRITNEYLOCK}
+
+rm -f "$NOTICE"
+cat > "$NOTICE" <<EOF
+Packages are currently being installed and indices rebuilt.
+Maintenance is automatic, starting at 07:52 and 19:52 UTC, and
+ending about an hour later.  This file is then removed.
+
+You should not mirror the archive during this period.
+EOF
+
+# Push merkels qa user, so the qa pages can show "dinstall is running" information
+echo "Telling merkels QA user that we start dinstall"
+ssh -2 -i ~dak/.ssh/push_merkel_qa  -o BatchMode=yes -o SetupTimeOut=90 -o ConnectTimeout=90 qa@merkel.debian.org sleep 1 || true
+ts "init"
+
+################################################################################
+
+echo "Creating pre-daily-cron-job backup of projectb database..."
+pg_dump projectb > $base/backup/dump_$(date +%Y.%m.%d-%H:%M:%S)
+ts "pg_dump1"
+
+################################################################################
+
+echo "Updating Bugs docu, Mirror list and mailing-lists.txt"
+cd $configdir
+$scriptsdir/update-bugdoctxt
+$scriptsdir/update-mirrorlists
+$scriptsdir/update-mailingliststxt
+$scriptsdir/update-pseudopackages.sh
+ts "External Updates"
+
+################################################################################
+
+echo "Doing automated p-u-new processing"
+cd $queuedir/p-u-new
+date -u -R >> REPORT
+dak process-new -a -C COMMENTS >> REPORT || true
+echo >> REPORT
+ts "p-u-new"
+
+echo "Doing automated o-p-u-new processing"
+cd $queuedir/o-p-u-new
+date -u -R >> REPORT
+dak process-new -a -C COMMENTS >> REPORT || true
+echo >> REPORT
+ts "o-p-u-new"
+
+################################################################################
+
+
+echo "Synchronizing i18n package descriptions"
+# First sync their newest data
+cd ${scriptdir}/i18nsync
+rsync -aq --delete --delete-after ddtp-sync:/does/not/matter . || true
+
+# Now check if we still know about the packages for which they created the files
+# is the timestamp signed by us?
+if $(gpgv --keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/pubring.gpg timestamp.gpg timestamp); then
+    # now read it. As its signed by us we are sure the content is what we expect, no need
+       # to do more here. And we only test -d a directory on it anyway.
+    TSTAMP=$(cat timestamp)
+    # do we have the dir still?
+    if [ -d ${scriptdir}/i18n/${TSTAMP} ]; then
+        # Lets check!
+        if ${scriptsdir}/ddtp-i18n-check.sh . ${scriptdir}/i18n/${TSTAMP}; then
+                       # Yay, worked, lets copy around
+                       for dir in lenny sid; do
+                               if [ -d dists/${dir}/ ]; then
+                                       cd dists/${dir}/main/i18n
+                                       rsync -aq --delete --delete-after  . ${ftpdir}/dists/${dir}/main/i18n/.
+                               fi
+                               cd ${scriptdir}/i18nsync
+                       done
+               else
+                       echo "ARRRR, bad guys, wrong files, ARRR"
+                       echo "Arf, Arf, Arf, bad guys, wrong files, arf, arf, arf" | mail debian-l10n-devel@lists.alioth.debian.org
+               fi
+    else
+               echo "ARRRR, missing the timestamp ${TSTAMP} directory, not updating i18n, ARRR"
+               echo "Arf, Arf, Arf, missing the timestamp ${TSTAMP} directory, not updating i18n, arf, arf, arf" | mail debian-l10n-devel@lists.alioth.debian.org
+       fi
+else
+    echo "ARRRRRRR, could not verify our timestamp signature, ARRR. Don't mess with our files, i18n guys, ARRRRR."
+       echo "Arf, Arf, Arf, could not verify our timestamp signature, arf. Don't mess with our files, i18n guys, arf, arf, arf" | mail debian-l10n-devel@lists.alioth.debian.org
+fi
+ts "i18n 1"
+
+################################################################################
+
+lockfile $LOCKAC
+lockac=1
+echo "Processing queue/accepted"
+cd $accepted
+rm -f REPORT
+dak process-accepted -pa *.changes | tee REPORT | \
+     mail -s "Install for $(date +%D)" ftpmaster@ftp-master.debian.org
+chgrp debadmin REPORT
+chmod 664 REPORT
+ts "accepted"
+
+echo "Checking for cruft in overrides"
+dak check-overrides
+rm -f $LOCKAC
+lockac=0
+
+echo "Fixing symlinks in $ftpdir"
+symlinks -d -r $ftpdir
+ts "cruft"
+
+echo "Generating suite file lists for apt-ftparchive"
+dak make-suite-file-list
+ts "make-suite-file-list"
+
+echo "Updating fingerprints"
+# Update fingerprints
+dak import-keyring -L /srv/keyring.debian.org/keyrings/debian-keyring.gpg || true
+ts "import-keyring"
+
+# Generate override files
+echo "Writing overrides into text files"
+cd $overridedir
+dak make-overrides
+
+# FIXME
+rm -f override.sid.all3
+for i in main contrib non-free main.debian-installer; do cat override.sid.$i >> override.sid.all3; done
+ts "overrides"
+
+
+# Generate Packages and Sources files
+echo "Generating Packages and Sources files"
+cd $configdir
+apt-ftparchive generate apt.conf
+ts "apt-ftparchive"
+
+# Generate *.diff/ incremental updates
+echo "Generating pdiff files"
+dak generate-index-diffs
+ts "pdiff"
+
+# Generate Release files
+echo "Generating Release files"
+dak generate-releases
+ts "release files"
+
+# Clean out old packages
+echo "Cleanup old packages/files"
+dak clean-suites
+dak clean-queues
+ts "cleanup"
+
+# Needs to be rebuilt, as files have moved.  Due to unaccepts, we need to
+# update this before wanna-build is updated.
+echo "Regenerating wanna-build/buildd information"
+psql projectb -A -t -q -c "SELECT filename FROM queue_build WHERE suite = 5 AND queue = 0 AND in_queue = true AND filename ~ 'd(sc|eb)$'" > $dbdir/dists/unstable_accepted.list
+symlinks -d /srv/incoming.debian.org/buildd > /dev/null
+apt-ftparchive generate apt.conf.buildd
+ts "buildd"
+
+echo "Running various scripts from $scriptsdir"
+cd $scriptsdir
+./mkmaintainers
+./copyoverrides
+./mklslar
+./mkfilesindices
+./mkchecksums
+ts "scripts"
+
+# (Re)generate the hardlinked mirror directory for "public" buildd / mirror access
+echo "Regenerating mirror/ hardlink fun"
+cd ${mirrordir}
+rsync -aH --link-dest ${ftpdir} --delete --delete-after --ignore-errors ${ftpdir}/. .
+ts "mirror hardlinks"
+
+echo "Trigger daily wanna-build run"
+ssh -o BatchMode=yes -o SetupTimeOut=90 -o ConnectTimeout=90 wbadm@buildd /org/wanna-build/trigger.daily || echo "W-B trigger.daily failed" | mail -s "W-B Daily trigger failed" ftpmaster@ftp-master.debian.org
+ts "w-b"
+
+rm -f $NOTICE
+rm -f $LOCKCU
+
+ts "locked part finished"
+
+################################################################################
+
+echo "Creating post-daily-cron-job backup of projectb database..."
+POSTDUMP=$base/backup/dump_$(date +%Y.%m.%d-%H:%M:%S)
+pg_dump projectb > $POSTDUMP
+(cd $base/backup; ln -sf $POSTDUMP current)
+ts "pg_dump2"
+
+################################################################################
+
+
+echo "Expiring old database dumps..."
+(cd $base/backup; $scriptsdir/expire_dumps -d . -p -f "dump_*")
+ts "expire_dumps"
+
+################################################################################
+
+
+# Send a report on NEW/BYHAND packages
+echo "Nagging ftpteam about NEW/BYHAND packages"
+dak queue-report | mail -e -s "NEW and BYHAND on $(date +%D)" ftpmaster@ftp-master.debian.org
+# and one on crufty packages
+echo "Sending information about crufty packages"
+dak cruft-report > $webdir/cruft-report-daily.txt
+dak cruft-report -s experimental >> $webdir/cruft-report-daily.txt
+cat $webdir/cruft-report-daily.txt | mail -e -s "Debian archive cruft report for $(date +%D)" ftpmaster@ftp-master.debian.org
+ts "reports"
+
+echo "Updating DM html page"
+$scriptsdir/dm-monitor >$webdir/dm-uploaders.html
+
+################################################################################
+
+# Push katie@merkel so it syncs the projectb there. Returns immediately, the sync runs detached
+echo "Trigger merkels projectb sync"
+ssh -2 -o BatchMode=yes -o SetupTimeOut=30 -o ConnectTimeout=30 -i ~/.ssh/push_merkel_projectb katie@merkel.debian.org sleep 1 || true
+ts "merkel projectb push"
+
+################################################################################
+
+
+ulimit -m 90000 -d 90000 -s 10000 -v 200000
+
+echo "Using run-parts to run scripts in $base/scripts/distmnt"
+run-parts --report $base/scripts/distmnt
+ts "run-parts"
+
+echo "Exporting package data foo for i18n project"
+STAMP=$(date "+%Y%m%d%H%M")
+mkdir -p ${scriptdir}/i18n/${STAMP}
+cd ${scriptdir}/i18n/${STAMP}
+dak control-suite -l stable > etch
+dak control-suite -l testing > lenny
+dak control-suite -l unstable > sid
+echo "${STAMP}" > timestamp
+gpg --secret-keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/secring.gpg --keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/pubring.gpg --no-options --batch --no-tty --armour --default-key 6070D3A1 --detach-sign -o timestamp.gpg timestamp
+rm -f md5sum
+md5sum * > md5sum
+cd ${webdir}/
+ln -sfT ${scriptdir}/i18n/${STAMP} i18n
+
+cd ${scriptdir}
+find ./i18n -mtime +2 -mindepth 1 -maxdepth 1 -not -name "${STAMP}" -type d -print0 | xargs --no-run-if-empty -0 rm -rf
+ts "i18n 2"
+
+echo "Daily cron scripts successful."
+
+# Stats pr0n
+echo "Updating stats data"
+cd $configdir
+$scriptsdir/update-ftpstats $base/log/* > $base/misc/ftpstats.data
+R --slave --vanilla < $base/misc/ftpstats.R
+ts "stats"
+
+# Remove the britney lock
+rm -f ${BRITNEYLOCK}
+
+# Clean up apt-ftparchive's databases
+echo "Clean up apt-ftparchive's databases"
+cd $configdir
+apt-ftparchive -q clean apt.conf
+ts "apt-ftparchive cleanup"
+
+# Compress psql backups
+echo "Compress old psql backups"
+(cd $base/backup/
+       find -maxdepth 1 -mindepth 1 -type f -name 'dump_*' \! -name '*.bz2' \! -name '*.gz' -mtime +1 | 
+       while read dumpname; do
+               echo "Compressing $dumpname"
+               bzip2 -9 "$dumpname"
+       done
+)
+ts "compress"
+
+echo "Removing old dinstall logfiles"
+(cd $logdir
+       find -maxdepth 1 -mindepth 1 -type f -name 'dinstall_*' -mtime +60 | 
+       while read dumpname; do
+               echo "Removing $dumpname"
+               rm -f "$dumpname"
+       done
+
+       find -maxdepth 1 -mindepth 1 -type f -name 'weekly_*' -mtime +60 | 
+       while read dumpname; do
+               echo "Removing $dumpname"
+               rm -f "$dumpname"
+       done
+)
+ts "logremove"
+
+echo "Finally, all is done, sending mail and compressing logfile"
+exec > /dev/null 2>&1
+
+cat "$LOGFILE" | mail -s "Log for dinstall run of ${NOW}" cron@ftp-master.debian.org
+bzip2 -9 "$LOGFILE"
+
+################################################################################
diff --git a/config/debian/cron.hourly b/config/debian/cron.hourly
new file mode 100755 (executable)
index 0000000..96d8ce4
--- /dev/null
@@ -0,0 +1,17 @@
+#! /bin/sh
+#
+# Executed hourly via cron, out of dak's crontab.
+
+set -e
+set -u
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+
+date -u > $ftpdir/project/trace/ftp-master.debian.org
+echo "Using dak v1" >> $ftpdir/project/trace/ftp-master.debian.org
+echo "Running on host: $(hostname -f)" >> $ftpdir/project/trace/ftp-master.debian.org
+dak import-users-from-passwd
+dak queue-report -n > $webdir/new.html
+dak show-deferred > ${webdir}/deferred.html
+cd $queuedir/new ; dak show-new *.changes > /dev/null
+$scriptsdir/generate-di
diff --git a/config/debian/cron.monthly b/config/debian/cron.monthly
new file mode 100755 (executable)
index 0000000..d9e0099
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Run at the beginning of the month via cron, out of dak's crontab.
+
+set -e
+set -u
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+
+################################################################################
+
+DATE=`date -d yesterday +%y%m`
+
+cd /srv/ftp.debian.org/mail/archive
+for m in mail bxamail; do
+    if [ -f $m ]; then
+        mv $m ${m}-$DATE
+        sleep 20
+        gzip -9 ${m}-$DATE
+        chgrp $ftpgroup ${m}-$DATE.gz
+        chmod 660 ${m}-$DATE.gz
+    fi;
+done
+
+################################################################################
diff --git a/config/debian/cron.unchecked b/config/debian/cron.unchecked
new file mode 100755 (executable)
index 0000000..41b6640
--- /dev/null
@@ -0,0 +1,79 @@
+#! /bin/sh
+
+set -e
+set -u
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+
+LOCKDAILY=""
+LOCKFILE="$lockdir/unchecked.lock"
+NOTICE="$lockdir/daily.lock"
+
+if [ -e $NOTICE ]; then exit 0; fi
+
+STAMP=$(date "+%Y%m%d%H%M")
+
+cleanup() {
+    rm -f "$LOCKFILE"
+    if [ ! -z "$LOCKDAILY" ]; then
+       rm -f "$NOTICE"
+    fi
+}
+
+# only run one cron.unchecked
+if lockfile -r3 $LOCKFILE; then
+    trap cleanup 0
+    cd $unchecked
+
+    changes=$(find . -maxdepth 1 -mindepth 1 -type f -name \*.changes | sed -e "s,./,," | xargs)
+    report=$queuedir/REPORT
+    timestamp=$(date "+%Y-%m-%d %H:%M")
+
+    if [ ! -z "$changes" ]; then
+       echo "$timestamp": "$changes"  >> $report
+       dak process-unchecked -a $changes >> $report
+       echo "--" >> $report
+
+       # sync with debbugs
+    rsync -aq -e "ssh -o Batchmode=yes -o ConnectTimeout=30 -o SetupTimeout=30" --remove-source-files  $queuedir/bts_version_track/ bugs-sync:/org/bugs.debian.org/versions/queue/ftp-master/ 2>/dev/null && touch $lockdir/synced_bts_version || true
+
+       NOW=$(date +%s)
+       TSTAMP=$(stat -c %Y $lockdir/synced_bts_version)
+       DIFF=$(( NOW - TSTAMP ))
+       if [ $DIFF -ge 259200 ]; then
+               echo "Kids, you tried your best and you failed miserably. The lesson is, never try. (Homer Simpson)"
+       fi
+
+       if lockfile -r3 $NOTICE; then
+           LOCKDAILY="YES"
+           psql projectb -A -t -q -c "SELECT filename FROM queue_build WHERE queue = 0 AND suite = 5 AND in_queue = true AND filename ~ 'd(sc|eb)$'" > $dbdir/dists/unstable_accepted.list
+           cd $overridedir
+           dak make-overrides &>/dev/null
+           rm -f override.sid.all3 override.sid.all3.src
+           for i in main contrib non-free main.debian-installer; do
+               cat override.sid.$i >> override.sid.all3
+               if [ "$i" != "main.debian-installer" ]; then
+                   cat override.sid.$i.src >> override.sid.all3.src
+               fi
+           done
+           cd $configdir
+           apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate apt.conf.buildd
+
+           cd  ${incoming}
+           rm -f buildd/Release*
+           apt-ftparchive -qq -o APT::FTPArchive::Release::Origin="Debian" -o APT::FTPArchive::Release::Label="Debian" -o APT::FTPArchive::Release::Description="buildd incoming" -o APT::FTPArchive::Release::Architectures="${archs}" release buildd > Release
+           gpg --secret-keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/secring.gpg --keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/pubring.gpg --no-options --batch --no-tty --armour --default-key 6070D3A1 --detach-sign -o Release.gpg Release 
+               mv Release* buildd/.
+
+           cd ${incoming}
+           mkdir -p tree/${STAMP}
+           cp -al ${incoming}/buildd/. tree/${STAMP}/
+           ln -sfT tree/${STAMP} ${incoming}/builddweb
+           find ./tree -mindepth 1 -maxdepth 1 -not -name "${STAMP}" -type d -print0 | xargs --no-run-if-empty -0 rm -rf
+
+           . $configdir/cron.buildd
+       fi
+    else
+               echo "$timestamp": Nothing to do >> $report
+    fi
+fi
diff --git a/config/debian/cron.weekly b/config/debian/cron.weekly
new file mode 100755 (executable)
index 0000000..4baaf46
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Run once a week via cron, out of dak's crontab.
+
+set -e
+set -u
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+
+# Start logging
+NOW=`date "+%Y.%m.%d-%H:%M:%S"`
+LOGFILE="$logdir/weekly_${NOW}.log"
+exec > "$LOGFILE" 2>&1
+
+cleanup() {
+  echo "Cleanup"
+  rm -f "$LOGFILE"
+}
+trap cleanup 0
+
+################################################################################
+
+# Purge empty directories
+echo "Purging empty directories in $ftpdir/pool/"
+
+if [ ! -z "$(find $ftpdir/pool/ -type d -empty)" ]; then
+   find $ftpdir/pool/ -type d -empty | xargs rmdir;
+fi
+
+# Split queue/done
+echo "Splitting queue/done"
+dak split-done > /dev/null
+
+# Vacuum the database
+echo "VACUUM; VACUUM ANALYZE;" | psql --no-psqlrc projectb 2>&1 | grep -v "^NOTICE:  Skipping.*only table owner can VACUUM it$"
+
+# Do git cleanup stuff
+echo "Doing git stuff"
+cd /org/ftp.debian.org/git/dak.git
+git gc --prune
+git update-server-info
+# now workaround a git bug not honoring the setup in logs/*
+# (fix in development, but until it reached backports.org.......)
+chmod -R g+w logs/
+
+# Clean up apt-ftparchive's databases
+cd $configdir
+echo "Cleanup apt-ftparchive's database"
+apt-ftparchive -q clean apt.conf
+apt-ftparchive -q clean apt.conf.buildd
+
+# Update wanna-build dump
+echo "Update wanna-build database dump"
+/org/ftp.debian.org/scripts/nfu/get-w-b-db
+
+echo "Finally, all is done, compressing logfile"
+exec > /dev/null 2>&1
+
+bzip2 -9 "$LOGFILE"
+
+
+################################################################################
diff --git a/config/debian/dak.conf b/config/debian/dak.conf
new file mode 100644 (file)
index 0000000..9f0c7b0
--- /dev/null
@@ -0,0 +1,735 @@
+Dinstall
+{
+   GPGKeyring {
+      "/srv/keyring.debian.org/keyrings/debian-keyring.gpg"; 
+      "/srv/keyring.debian.org/keyrings/debian-keyring.pgp";
+      "/srv/ftp.debian.org/keyrings/debian-maintainers.gpg";
+   };
+   SigningKeyring "/srv/ftp.debian.org/s3kr1t/dot-gnupg/secring.gpg";
+   SigningPubKeyring "/srv/ftp.debian.org/s3kr1t/dot-gnupg/pubring.gpg";
+   SigningKeyIds "6070D3A1";
+   SendmailCommand "/usr/sbin/sendmail -odq -oi -t";
+   MyEmailAddress "Debian Installer <installer@ftp-master.debian.org>";
+   MyAdminAddress "ftpmaster@debian.org";
+   MyHost "debian.org";  // used for generating user@my_host addresses in e.g. manual_reject()
+   MyDistribution "Debian"; // Used in emails
+   BugServer "bugs.debian.org";
+   PackagesServer "packages.debian.org";
+   TrackingServer "packages.qa.debian.org";
+   LockFile "/srv/ftp.debian.org/lock/dinstall.lock";
+   Bcc "archive@ftp-master.debian.org";
+   FutureTimeTravelGrace 28800; // 8 hours
+   PastCutoffYear "1984";
+   SkipTime 300;
+   BXANotify "true";
+   CloseBugs "true";
+   OverrideDisparityCheck "true";
+   StableDislocationSupport "false";
+   DefaultSuite "unstable";
+   UserExtensions "/srv/ftp.debian.org/dak/config/debian/extensions.py";
+   QueueBuildSuites
+   {
+     unstable;
+   };
+   Reject
+   {
+     NoSourceOnly "true";
+     ReleaseTransitions "/srv/ftp.debian.org/web/transitions.yaml";
+   };
+};
+
+Transitions
+{
+   TempPath "/srv/ftp.debian.org/tmp/";
+};
+
+Generate-Index-Diffs
+{
+   Options
+   {
+     TempDir "/srv/ftp.debian.org/tiffani";
+     MaxDiffs { Default 14; };
+   };
+};
+
+Override
+{
+   MyEmailAddress "Debian FTP Masters <ftpmaster@ftp-master.debian.org>";
+};
+
+Mirror-Split
+{
+  FTPPath "/srv/ftp.debian.org/ftp";
+  TreeRootPath "/srv/ftp.debian.org/scratch/dsilvers/treeroots";
+  TreeDatabasePath "/srv/ftp.debian.org/scratch/dsilvers/treedbs";
+  BasicTrees { alpha; arm; hppa; hurd-i386; i386; ia64; mips; mipsel; powerpc; s390; sparc; m68k };
+  CombinationTrees
+  {
+    popular { i386; powerpc; all; source; };
+    source { source; };
+    everything { source; all; alpha; arm; hppa; hurd-i386; i386; ia64; mips; mipsel; powerpc; s390; sparc; m68k; };
+  };
+};
+
+Show-New
+{
+  HTMLPath "/srv/ftp.debian.org/web/new/";
+}
+
+Show-Deferred
+{
+  LinkPath "/srv/ftp.debian.org/web/deferred/";
+  DeferredQueue "/srv/queued/DEFERRED/";
+}
+
+Import-Users-From-Passwd
+{
+  ValidGID "800";
+  // Comma separated list of users who are in Postgres but not the passwd file
+  KnownPostgres "postgres,dak,katie,release";
+};
+
+Clean-Queues
+{
+  Options
+  {
+    Days 14;
+   };
+ MorgueSubDir "queues";
+};
+
+Control-Overrides
+{
+  Options
+  {
+    Component "main";
+    Suite "unstable";
+    Type "deb";
+   };
+
+ ComponentPosition "prefix"; // Whether the component is prepended or appended to the section name
+};
+
+Rm
+{
+  Options
+  {
+    Suite "unstable";
+   };
+
+   MyEmailAddress "Debian Archive Maintenance <ftpmaster@ftp-master.debian.org>";
+   LogFile "/srv/ftp.debian.org/web/removals.txt";
+   Bcc "removed-packages@qa.debian.org";
+};
+
+Import-Archive
+{
+  ExportDir "/srv/ftp.debian.org/dak/import-archive-files/";
+};
+
+Reject-Proposed-Updates
+{
+   StableRejector "the Stable Release Team";
+   StableMail "debian-release@lists.debian.org";
+   MoreInfoURL "http://release.debian.org/stable/4.0/4.0r5/";
+};
+
+Import-LDAP-Fingerprints
+{
+  LDAPDn "ou=users,dc=debian,dc=org";
+  LDAPServer "db.debian.org";
+  ExtraKeyrings
+  {
+    "/srv/keyring.debian.org/keyrings/removed-keys.pgp";
+    "/srv/keyring.debian.org/keyrings/removed-keys.gpg";
+    "/srv/keyring.debian.org/keyrings/extra-keys.pgp";
+  };
+  KeyServer "wwwkeys.eu.pgp.net";
+};
+
+Clean-Suites
+{
+  // How long (in seconds) dead packages are left before being killed
+  StayOfExecution 129600; // 1.5 days
+  QueueBuildStayOfExecution 86400; // 24 hours
+  MorgueSubDir "pool";
+};
+
+Process-New
+{
+  AcceptedLockFile "/srv/ftp.debian.org/lock/unchecked.lock";
+};
+
+Check-Overrides
+{
+  OverrideSuites
+  {
+    Stable
+    {
+      Process "0";
+    };
+
+    Testing
+    {
+      Process "1";
+      OriginSuite "Unstable";
+    };
+
+    Unstable
+    {
+      Process "1";
+    };
+  };
+};
+
+Suite
+{
+  Stable
+  {
+       Components
+       {
+         main;
+         contrib;
+         non-free;
+       };
+       Architectures
+       {
+         source;
+         all;
+         alpha;
+         amd64;
+         arm;
+         hppa;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "debian-changes@lists.debian.org";
+       // Version "4.0r1";
+       Origin "Debian";
+       // Description "Debian 4.0r1 Released 15 August 2007";
+       CodeName "etch";
+       OverrideCodeName "etch";
+       Priority "5";
+       Untouchable "1";
+       ChangeLogBase "dists/stable/";
+       UdebComponents
+       {
+         main;
+         non-free;
+       };
+  };
+
+  Proposed-Updates
+  {
+       Components
+       {
+         main;
+         contrib;
+         non-free;
+       };
+       Architectures
+       {
+         source;
+         all;
+         alpha;
+         amd64;
+         arm;
+         hppa;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "debian-changes@lists.debian.org";
+       CopyChanges "dists/proposed-updates/";
+       CopyDotDak "/srv/ftp.debian.org/queue/proposed-updates/";
+       CommentsDir "/srv/ftp.debian.org/queue/p-u-new/COMMENTS/";
+       Version "4.0-updates";
+       Origin "Debian";
+       Description "Debian 4.0 Proposed Updates - Not Released";
+       CodeName "etch-proposed-updates";
+       OverrideCodeName "etch";
+       OverrideSuite "stable";
+       ValidTime 604800; // 7 days
+       Priority "4";
+       VersionChecks
+       {
+         MustBeNewerThan
+         {
+           Stable;
+         };
+         MustBeOlderThan
+         {
+           Testing;
+           Unstable;
+           Experimental;
+         };
+         Enhances
+          {
+           Stable;
+         };
+       };
+       UdebComponents
+       {
+         main;
+       };
+  };
+
+  Testing
+  {
+       Components
+       {
+         main;
+         contrib;
+         non-free;
+       };
+       Architectures
+       {
+         source;
+         all;
+         alpha;
+         amd64;
+         arm;
+         armel;
+         hppa;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "debian-testing-changes@lists.debian.org";
+       Origin "Debian";
+       Description "Debian Testing distribution - Not Released";
+       CodeName "lenny";
+       OverrideCodeName "lenny";
+       ValidTime 604800; // 7 days
+       Priority "5";
+       UdebComponents
+       {
+         main;
+         non-free;
+       };
+  };
+
+  Testing-Proposed-Updates
+  {
+       Components
+       {
+         main;
+         contrib;
+         non-free;
+       };
+       Architectures
+       {
+         source;
+         all;
+         alpha;
+         amd64;
+         arm;
+         armel;
+         hppa;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "debian-testing-changes@lists.debian.org";
+       Origin "Debian";
+       Description "Debian Testing distribution updates - Not Released";
+       CodeName "lenny-proposed-updates";
+       OverrideCodeName "lenny";
+       OverrideSuite "testing";
+       ValidTime 604800; // 7 days
+       Priority "6";
+       VersionChecks
+       {
+         MustBeNewerThan
+         {
+           Stable;
+           Proposed-Updates;
+           Testing;
+         };
+         MustBeOlderThan
+         {
+           Unstable;
+           Experimental;
+         };
+         Enhances
+          {
+           Testing;
+          };
+       };
+       UdebComponents
+       {
+         main;
+         non-free;
+       };
+  };
+
+  Etch-m68k
+  {
+       Components
+       {
+         main;
+         contrib;
+         non-free;
+       };
+       Architectures
+       {
+         source;
+         all;
+         m68k;
+       };
+       Announce "debian-testing-changes@lists.debian.org";
+       Origin "Debian";
+       Description "Debian Etch for m68k - Not Released";
+       CodeName "etch-m68k";
+       OverrideCodeName "etch";
+       Priority "5";
+       UdebComponents
+       {
+         main;
+         non-free;
+       };
+  };
+
+  Unstable
+  {
+       Components
+       {
+         main;
+         contrib;
+         non-free;
+       };
+       Architectures
+       {
+         source;
+         all;
+         alpha;
+         amd64;
+         arm;
+         armel;
+         hppa;
+         hurd-i386;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "debian-devel-changes@lists.debian.org";
+       Origin "Debian";
+       Description "Debian Unstable - Not Released";
+       CodeName "sid";
+       OverrideCodeName "sid";
+       ValidTime 604800; // 7 days
+       Priority "7";
+       VersionChecks
+       {
+         MustBeNewerThan
+         {
+           Stable;
+           Proposed-Updates;
+           Testing;
+           Testing-Proposed-Updates;
+         };
+       };
+       UdebComponents
+       {
+         main;
+         non-free;
+       };
+  };
+
+  Experimental
+  {
+       Components
+       {
+         main;
+         contrib;
+         non-free;
+       };
+       Architectures
+       {
+         source;
+         all;
+         alpha;
+         amd64;
+         arm;
+         armel;
+         hppa;
+         hurd-i386;
+         i386;
+         ia64;
+         mips;
+         mipsel;
+         powerpc;
+         s390;
+         sparc;
+       };
+       Announce "debian-devel-changes@lists.debian.org";
+       Origin "Debian";
+       Description "Experimental packages - not released; use at your own risk.";
+       CodeName "experimental";
+       NotAutomatic "yes";
+       OverrideCodeName "sid";
+       OverrideSuite "unstable";
+       ValidTime 604800; // 7 days
+       Priority "0";
+       VersionChecks
+       {
+         MustBeNewerThan
+         {
+           Stable;
+           Proposed-Updates;
+           Testing;
+           Testing-Proposed-Updates;
+           Unstable;
+         };
+       };
+       UdebComponents
+       {
+         main;
+         non-free;
+       };
+  };
+
+};
+
+SuiteMappings
+{
+// "propup-version oldstable-security stable testing testing-proposed-updates unstable";
+ "propup-version stable-security testing testing-proposed-updates unstable";
+ "propup-version testing-security unstable";
+// "map oldstable oldstable-proposed-updates";
+// "map oldstable-security oldstable-proposed-updates";
+ "map stable proposed-updates";
+ "map stable-security proposed-updates";
+ "map stable-proposed-updates proposed-updates";
+ "map-unreleased oldstable unstable";
+ "map-unreleased stable unstable";
+ "map-unreleased proposed-updates unstable";
+ "map testing testing-proposed-updates";
+ "map testing-security testing-proposed-updates";
+ "map-unreleased testing unstable";
+ "map-unreleased testing-proposed-updates unstable";
+};
+
+AutomaticByHandPackages {
+  "debian-installer-images" {
+    Source "debian-installer";
+    Section "raw-installer";
+    Extension "tar.gz";
+    Script "/srv/ftp.debian.org/dak/scripts/debian/byhand-di";
+  };
+
+  "debian-maintainers" {
+    Source "debian-maintainers";
+    Section "raw-keyring";
+    Extension "gpg";
+    Script "/srv/ftp.debian.org/dak/scripts/debian/byhand-dm";
+  };
+
+  "tag-overrides" {
+    Source "tag-overrides";
+    Section "byhand";
+    Extension "tar.gz";
+    Script "/srv/ftp.debian.org/dak/scripts/debian/byhand-tag";
+  };
+
+  "task-overrides" {
+    Source "tasksel";
+    Section "byhand";
+    Extension "tar.gz";
+    Script "/srv/ftp.debian.org/dak/scripts/debian/byhand-task";
+  };
+};
+
+Dir
+{
+  Root "/srv/ftp.debian.org/ftp/";
+  Pool "/srv/ftp.debian.org/ftp/pool/";
+  Templates "/srv/ftp.debian.org/dak/templates/";
+  PoolRoot "pool/";
+  Lists "/srv/ftp.debian.org/database/dists/";
+  Log "/srv/ftp.debian.org/log/";
+  Lock "/srv/ftp.debian.org/lock";
+  Morgue "/srv/ftp.debian.org/morgue/";
+  MorgueReject "reject";
+  Override "/srv/ftp.debian.org/scripts/override/";
+  QueueBuild "/srv/incoming.debian.org/buildd/";
+  UrgencyLog "/srv/release.debian.org/britney/input/urgencies/";
+  Queue
+  {
+    Accepted "/srv/ftp.debian.org/queue/accepted/";
+    Byhand "/srv/ftp.debian.org/queue/byhand/";
+    ProposedUpdates "/srv/ftp.debian.org/queue/p-u-new/";
+    OldProposedUpdates "/srv/ftp.debian.org/queue/o-p-u-new/";
+    Done "/srv/ftp.debian.org/queue/done/";
+    Holding "/srv/ftp.debian.org/queue/holding/";
+    New "/srv/ftp.debian.org/queue/new/";
+    Reject "/srv/ftp.debian.org/queue/reject/";
+    Unchecked "/srv/ftp.debian.org/queue/unchecked/";
+    BTSVersionTrack "/srv/ftp.debian.org/queue/bts_version_track/";
+  };
+};
+
+DB
+{
+  Name "projectb";
+  Host "";
+  Port -1;
+};
+
+Architectures
+{
+  source "Source";
+  all "Architecture Independent";
+  alpha "DEC Alpha";
+  hurd-i386 "Intel ia32 running the HURD";
+  hppa "HP PA RISC";
+  amd64 "AMD64";
+  arm "ARM";
+  i386 "Intel ia32";
+  ia64 "Intel ia64";
+  m68k "Motorola Mc680x0";
+  mips "MIPS (Big Endian)";
+  mipsel "MIPS (Little Endian)";
+  powerpc "PowerPC";
+  s390 "IBM S/390";
+  sh "Hitatchi SuperH";
+  sparc "Sun SPARC/UltraSPARC";
+};
+
+Archive
+{
+  ftp-master
+  {
+    OriginServer "ftp-master.debian.org";
+    PrimaryMirror "ftp.debian.org";
+    Description "Master Archive for the Debian project";
+  };
+};
+
+Component
+{
+  main
+  {
+       Description "Main";
+       MeetsDFSG "true";
+  };
+
+  contrib
+  {
+       Description "Contrib";
+       MeetsDFSG "true";
+  };
+
+  non-free
+  {
+       Description "Software that fails to meet the DFSG";
+       MeetsDFSG "false";
+  };
+
+  mixed  // **NB:** only used for overrides; not yet used in other code
+  {
+       Description "Legacy Mixed";
+       MeetsDFSG "false";
+  };
+};
+
+Section
+{
+  admin;
+  base;
+  comm;
+  debian-installer;
+  devel;
+  doc;
+  editors;
+  embedded;
+  electronics;
+  games;
+  gnome;
+  graphics;
+  hamradio;
+  interpreters;
+  kde;
+  libdevel;
+  libs;
+  mail;
+  math;
+  misc;
+  net;
+  news;
+  oldlibs;
+  otherosfs;
+  perl;
+  python;
+  science;
+  shells;
+  sound;
+  tex;
+  text;
+  utils;
+  web;
+  x11;
+};
+
+Priority
+{
+  required 1;
+  important 2;
+  standard 3;
+  optional 4;
+  extra 5;
+  source 0; // i.e. unused
+};
+
+OverrideType
+{
+  deb;
+  udeb;
+  dsc;
+};
+
+Location
+{
+
+  // Pool locations on ftp-master.debian.org
+  /srv/ftp.debian.org/ftp/pool/
+    {
+      Archive "ftp-master";
+      Type "pool";
+    };
+
+};
+
+Urgency
+{
+  Default "low";
+  Valid
+  {
+    low;
+    medium;
+    high;
+    emergency;
+    critical;
+  };
+};
diff --git a/config/debian/extensions.py b/config/debian/extensions.py
new file mode 100644 (file)
index 0000000..1e7ea43
--- /dev/null
@@ -0,0 +1,100 @@
+import sys, os, textwrap
+
+import apt_pkg
+import daklib.utils, daklib.database
+import syck
+
+import daklib.extensions
+from daklib.extensions import replace_dak_function
+
+def check_transition():
+    changes = dak_module.changes
+    reject = dak_module.reject
+    Cnf = dak_module.Cnf
+
+    sourcepkg = changes["source"]
+
+    # No sourceful upload -> no need to do anything else, direct return
+    # We also work with unstable uploads, not experimental or those going to some
+    # proposed-updates queue
+    if "source" not in changes["architecture"] or "unstable" not in changes["distribution"]:
+        return
+
+    # Also only check if there is a file defined (and existant) with
+    # checks.
+    transpath = Cnf.get("Dinstall::Reject::ReleaseTransitions", "")
+    if transpath == "" or not os.path.exists(transpath):
+        return
+
+    # Parse the yaml file
+    sourcefile = file(transpath, 'r')
+    sourcecontent = sourcefile.read()
+    try:
+        transitions = syck.load(sourcecontent)
+    except syck.error, msg:
+        # This shouldn't happen, there is a wrapper to edit the file which
+        # checks it, but we prefer to be safe than ending up rejecting
+        # everything.
+        daklib.utils.warn("Not checking transitions, the transitions file is broken: %s." % (msg))
+        return
+
+    # Now look through all defined transitions
+    for trans in transitions:
+        t = transitions[trans]
+        source = t["source"]
+        expected = t["new"]
+
+        # Will be None if nothing is in testing.
+        current = daklib.database.get_suite_version(source, "testing")
+        if current is not None:
+            compare = apt_pkg.VersionCompare(current, expected)
+
+        if current is None or compare < 0:
+            # This is still valid, the current version in testing is older than
+            # the new version we wait for, or there is none in testing yet
+
+            # Check if the source we look at is affected by this.
+            if sourcepkg in t['packages']:
+                # The source is affected, lets reject it.
+
+                rejectmsg = "%s: part of the %s transition.\n\n" % (
+                    sourcepkg, trans)
+
+                if current is not None:
+                    currentlymsg = "at version %s" % (current)
+                else:
+                    currentlymsg = "not present in testing"
+
+                rejectmsg += "Transition description: %s\n\n" % (t["reason"])
+
+                rejectmsg += "\n".join(textwrap.wrap("""Your package
+is part of a testing transition designed to get %s migrated (it is
+currently %s, we need version %s).  This transition is managed by the
+Release Team, and %s is the Release-Team member responsible for it.
+Please mail debian-release@lists.debian.org or contact %s directly if you
+need further assistance.  You might want to upload to experimental until this
+transition is done."""
+                        % (source, currentlymsg, expected,t["rm"], t["rm"])))
+
+                reject(rejectmsg + "\n")
+                return
+
+@replace_dak_function("process-unchecked", "check_signed_by_key")
+def check_signed_by_key(oldfn):
+    changes = dak_module.changes
+    reject = dak_module.reject
+
+    if changes["source"] == "dpkg":
+        fpr = changes["fingerprint"]
+        (uid, uid_name) = dak_module.lookup_uid_from_fingerprint(fpr)
+        if fpr == "5906F687BD03ACAD0D8E602EFCF37657" or uid == "iwj":
+            reject("Upload blocked due to hijack attempt 2008/03/19")
+
+            # NB: 1.15.0, 1.15.2 signed by this key targetted at unstable
+            #     have been made available in the wild, and should remain
+            #     blocked until Debian's dpkg has revved past those version
+            #     numbers
+
+    oldfn()
+
+    check_transition()
diff --git a/config/debian/vars b/config/debian/vars
new file mode 100644 (file)
index 0000000..02fa612
--- /dev/null
@@ -0,0 +1,31 @@
+# locations used by many scripts
+
+base=/srv/ftp.debian.org
+ftpdir=$base/ftp
+webdir=$base/web
+indices=$ftpdir/indices
+archs="alpha amd64 arm armel hppa hurd-i386 i386 ia64 mips mipsel powerpc s390 sparc"
+
+scriptdir=$base/scripts
+masterdir=$base/dak/
+configdir=$base/dak/config/debian/
+scriptsdir=$base/dak/scripts/debian/
+dbdir=$base/database/
+lockdir=$base/lock/
+overridedir=$scriptdir/override
+extoverridedir=$scriptdir/external-overrides
+logdir=$base/log/cron/
+
+queuedir=$base/queue/
+unchecked=$queuedir/unchecked/
+accepted=$queuedir/accepted/
+mirrordir=$base/mirror/
+incoming=$base/incoming
+
+ftpgroup=debadmin
+
+copyoverrides="etch.contrib etch.contrib.src etch.main etch.main.src etch.non-free etch.non-free.src etch.extra.main etch.extra.non-free etch.extra.contrib etch.main.debian-installer sid.contrib sid.contrib.src sid.main sid.main.debian-installer sid.main.src sid.non-free sid.non-free.src sid.extra.contrib sid.extra.main sid.extra.non-free lenny.contrib lenny.contrib.src lenny.main lenny.main.src lenny.non-free lenny.non-free.src lenny.extra.main lenny.extra.contrib lenny.extra.non-free"
+
+PATH=$masterdir:$PATH
+umask 022
+
diff --git a/config/examples/dak.conf b/config/examples/dak.conf
new file mode 100644 (file)
index 0000000..a47c5c0
--- /dev/null
@@ -0,0 +1,23 @@
+// Example /etc/dak/dak.conf
+
+Config
+{
+  // FQDN hostname
+  raff.debian.org
+  {
+
+    // Optional hostname as it appears in the database (if it differs
+    // from the FQDN hostname).
+    DatbaseHostname     "ftp-master";
+
+    // Optional filename of dak's config file; if not present, this
+    // file is assumed to contain dak config info.
+    DakConfig          "/org/ftp.debian.org/dak/config/debian/dak.conf";
+
+    // Optional filename of apt-ftparchive's config file; if not
+    // present, the file is assumed to be 'apt.conf' in the same
+    // directory as this file.
+    AptConfig          "/org/ftp.debian.org/dak/config/debian/apt.conf";
+  }
+
+}
diff --git a/dak/__init__.py b/dak/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/dak/check_archive.py b/dak/check_archive.py
new file mode 100755 (executable)
index 0000000..896ab1f
--- /dev/null
@@ -0,0 +1,471 @@
+#!/usr/bin/env python
+
+# Various different sanity checks
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+#   And, lo, a great and menacing voice rose from the depths, and with
+#   great wrath and vehemence it's voice boomed across the
+#   land... ``hehehehehehe... that *tickles*''
+#                                                       -- aj on IRC
+
+################################################################################
+
+import commands, os, pg, stat, sys, time
+import apt_pkg, apt_inst
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+db_files = {}
+waste = 0.0
+excluded = {}
+current_file = None
+future_files = {}
+current_time = time.time()
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak check-archive MODE
+Run various sanity checks of the archive and/or database.
+
+  -h, --help                show this help and exit.
+
+The following MODEs are available:
+
+  checksums          - validate the checksums stored in the database
+  files              - check files in the database against what's in the archive
+  dsc-syntax         - validate the syntax of .dsc files in the archive
+  missing-overrides  - check for missing overrides
+  source-in-one-dir  - ensure the source for each package is in one directory
+  timestamps         - check for future timestamps in .deb's
+  tar-gz-in-dsc      - ensure each .dsc lists a .tar.gz file
+  validate-indices   - ensure files mentioned in Packages & Sources exist
+  files-not-symlinks - check files in the database aren't symlinks
+  validate-builddeps - validate build-dependencies of .dsc files in the archive
+"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def process_dir (unused, dirname, filenames):
+    global waste, db_files, excluded
+
+    if dirname.find('/disks-') != -1 or dirname.find('upgrade-') != -1:
+        return
+    # hack; can't handle .changes files
+    if dirname.find('proposed-updates') != -1:
+        return
+    for name in filenames:
+        filename = os.path.abspath(dirname+'/'+name)
+        filename = filename.replace('potato-proposed-updates', 'proposed-updates')
+        if os.path.isfile(filename) and not os.path.islink(filename) and not db_files.has_key(filename) and not excluded.has_key(filename):
+            waste += os.stat(filename)[stat.ST_SIZE]
+            print "%s" % (filename)
+
+################################################################################
+
+def check_files():
+    global db_files
+
+    print "Building list of database files..."
+    q = projectB.query("SELECT l.path, f.filename, f.last_used FROM files f, location l WHERE f.location = l.id ORDER BY l.path, f.filename")
+    ql = q.getresult()
+
+    print "Missing files:"
+    db_files.clear()
+    for i in ql:
+        filename = os.path.abspath(i[0] + i[1])
+        db_files[filename] = ""
+        if os.access(filename, os.R_OK) == 0:
+            if i[2]:
+                print "(last used: %s) %s" % (i[2], filename)
+            else:
+                print "%s" % (filename)
+
+
+    filename = Cnf["Dir::Override"]+'override.unreferenced'
+    if os.path.exists(filename):
+        f = utils.open_file(filename)
+        for filename in f.readlines():
+            filename = filename[:-1]
+            excluded[filename] = ""
+
+    print "Existent files not in db:"
+
+    os.path.walk(Cnf["Dir::Root"]+'pool/', process_dir, None)
+
+    print
+    print "%s wasted..." % (utils.size_type(waste))
+
+################################################################################
+
+def check_dscs():
+    count = 0
+    suite = 'unstable'
+    for component in Cnf.SubTree("Component").List():
+        if component == "mixed":
+            continue
+        component = component.lower()
+        list_filename = '%s%s_%s_source.list' % (Cnf["Dir::Lists"], suite, component)
+        list_file = utils.open_file(list_filename)
+        for line in list_file.readlines():
+            f = line[:-1]
+            try:
+                utils.parse_changes(f, signing_rules=1)
+            except InvalidDscError, line:
+                utils.warn("syntax error in .dsc file '%s', line %s." % (f, line))
+                count += 1
+
+    if count:
+        utils.warn("Found %s invalid .dsc files." % (count))
+
+################################################################################
+
+def check_override():
+    for suite in [ "stable", "unstable" ]:
+        print suite
+        print "-"*len(suite)
+        print
+        suite_id = database.get_suite_id(suite)
+        q = projectB.query("""
+SELECT DISTINCT b.package FROM binaries b, bin_associations ba
+ WHERE b.id = ba.bin AND ba.suite = %s AND NOT EXISTS
+       (SELECT 1 FROM override o WHERE o.suite = %s AND o.package = b.package)"""
+                           % (suite_id, suite_id))
+        print q
+        q = projectB.query("""
+SELECT DISTINCT s.source FROM source s, src_associations sa
+  WHERE s.id = sa.source AND sa.suite = %s AND NOT EXISTS
+       (SELECT 1 FROM override o WHERE o.suite = %s and o.package = s.source)"""
+                           % (suite_id, suite_id))
+        print q
+
+################################################################################
+
+# Ensure that the source files for any given package is all in one
+# directory so that 'apt-get source' works...
+
+def check_source_in_one_dir():
+    # Not the most enterprising method, but hey...
+    broken_count = 0
+    q = projectB.query("SELECT id FROM source;")
+    for i in q.getresult():
+        source_id = i[0]
+        q2 = projectB.query("""
+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"""
+                            % (source_id))
+        first_path = ""
+        first_filename = ""
+        broken = 0
+        for j in q2.getresult():
+            filename = j[0] + j[1]
+            path = os.path.dirname(filename)
+            if first_path == "":
+                first_path = path
+                first_filename = filename
+            elif first_path != path:
+                symlink = path + '/' + os.path.basename(first_filename)
+                if not os.path.exists(symlink):
+                    broken = 1
+                    print "WOAH, we got a live one here... %s [%s] {%s}" % (filename, source_id, symlink)
+        if broken:
+            broken_count += 1
+    print "Found %d source packages where the source is not all in one directory." % (broken_count)
+
+################################################################################
+
+def check_checksums():
+    print "Getting file information from database..."
+    q = projectB.query("SELECT l.path, f.filename, f.md5sum, f.sha1sum, f.sha256sum, f.size FROM files f, location l WHERE f.location = l.id")
+    ql = q.getresult()
+
+    print "Checking file checksums & sizes..."
+    for i in ql:
+        filename = os.path.abspath(i[0] + i[1])
+        db_md5sum = i[2]
+        db_sha1sum = i[3]
+        db_sha256sum = i[4]
+        db_size = int(i[5])
+        try:
+            f = utils.open_file(filename)
+        except:
+            utils.warn("can't open '%s'." % (filename))
+            continue
+        md5sum = apt_pkg.md5sum(f)
+        size = os.stat(filename)[stat.ST_SIZE]
+        if md5sum != db_md5sum:
+            utils.warn("**WARNING** md5sum mismatch for '%s' ('%s' [current] vs. '%s' [db])." % (filename, md5sum, db_md5sum))
+        if size != db_size:
+            utils.warn("**WARNING** size mismatch for '%s' ('%s' [current] vs. '%s' [db])." % (filename, size, db_size))
+        # Until the main database is filled, we need to not spit 500,000 warnings
+        # every time we scan the archive.  Yet another hack (TM) which can go away
+        # once this is all working
+        if db_sha1sum is not None and db_sha1sum != '':
+            f.seek(0)
+            sha1sum = apt_pkg.sha1sum(f)
+            if sha1sum != db_sha1sum:
+                utils.warn("**WARNING** sha1sum mismatch for '%s' ('%s' [current] vs. '%s' [db])." % (filename, sha1sum, db_sha1sum))
+
+        if db_sha256sum is not None and db_sha256sum != '':
+            f.seek(0)
+            sha256sum = apt_pkg.sha256sum(f)
+            if sha256sum != db_sha256sum:
+                utils.warn("**WARNING** sha256sum mismatch for '%s' ('%s' [current] vs. '%s' [db])." % (filename, sha256sum, db_sha256sum))
+
+    print "Done."
+
+################################################################################
+#
+# Check all files for timestamps in the future; common from hardware
+# (e.g. alpha) which have far-future dates as their default dates.
+
+def Ent(Kind,Name,Link,Mode,UID,GID,Size,MTime,Major,Minor):
+    global future_files
+
+    if MTime > current_time:
+        future_files[current_file] = MTime
+        print "%s: %s '%s','%s',%u,%u,%u,%u,%u,%u,%u" % (current_file, Kind,Name,Link,Mode,UID,GID,Size, MTime, Major, Minor)
+
+def check_timestamps():
+    global current_file
+
+    q = projectB.query("SELECT l.path, f.filename FROM files f, location l WHERE f.location = l.id AND f.filename ~ '.deb$'")
+    ql = q.getresult()
+    db_files.clear()
+    count = 0
+    for i in ql:
+        filename = os.path.abspath(i[0] + i[1])
+        if os.access(filename, os.R_OK):
+            f = utils.open_file(filename)
+            current_file = filename
+            sys.stderr.write("Processing %s.\n" % (filename))
+            apt_inst.debExtract(f, Ent, "control.tar.gz")
+            f.seek(0)
+            apt_inst.debExtract(f, Ent, "data.tar.gz")
+            count += 1
+    print "Checked %d files (out of %d)." % (count, len(db_files.keys()))
+
+################################################################################
+
+def check_missing_tar_gz_in_dsc():
+    count = 0
+
+    print "Building list of database files..."
+    q = projectB.query("SELECT l.path, f.filename FROM files f, location l WHERE f.location = l.id AND f.filename ~ '.dsc$'")
+    ql = q.getresult()
+    if ql:
+        print "Checking %d files..." % len(ql)
+    else:
+        print "No files to check."
+    for i in ql:
+        filename = os.path.abspath(i[0] + i[1])
+        try:
+            # NB: don't enforce .dsc syntax
+            dsc = utils.parse_changes(filename)
+        except:
+            utils.fubar("error parsing .dsc file '%s'." % (filename))
+        dsc_files = utils.build_file_list(dsc, is_a_dsc=1)
+        has_tar = 0
+        for f in dsc_files.keys():
+            m = utils.re_issource.match(f)
+            if not m:
+                utils.fubar("%s not recognised as source." % (f))
+            ftype = m.group(3)
+            if ftype == "orig.tar.gz" or ftype == "tar.gz":
+                has_tar = 1
+        if not has_tar:
+            utils.warn("%s has no .tar.gz in the .dsc file." % (f))
+            count += 1
+
+    if count:
+        utils.warn("Found %s invalid .dsc files." % (count))
+
+
+################################################################################
+
+def validate_sources(suite, component):
+    filename = "%s/dists/%s/%s/source/Sources.gz" % (Cnf["Dir::Root"], suite, component)
+    print "Processing %s..." % (filename)
+    # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
+    temp_filename = utils.temp_filename()
+    (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
+    if (result != 0):
+        sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
+        sys.exit(result)
+    sources = utils.open_file(temp_filename)
+    Sources = apt_pkg.ParseTagFile(sources)
+    while Sources.Step():
+        source = Sources.Section.Find('Package')
+        directory = Sources.Section.Find('Directory')
+        files = Sources.Section.Find('Files')
+        for i in files.split('\n'):
+            (md5, size, name) = i.split()
+            filename = "%s/%s/%s" % (Cnf["Dir::Root"], directory, name)
+            if not os.path.exists(filename):
+                if directory.find("potato") == -1:
+                    print "W: %s missing." % (filename)
+                else:
+                    pool_location = utils.poolify (source, component)
+                    pool_filename = "%s/%s/%s" % (Cnf["Dir::Pool"], pool_location, name)
+                    if not os.path.exists(pool_filename):
+                        print "E: %s missing (%s)." % (filename, pool_filename)
+                    else:
+                        # Create symlink
+                        pool_filename = os.path.normpath(pool_filename)
+                        filename = os.path.normpath(filename)
+                        src = utils.clean_symlink(pool_filename, filename, Cnf["Dir::Root"])
+                        print "Symlinking: %s -> %s" % (filename, src)
+                        #os.symlink(src, filename)
+    sources.close()
+    os.unlink(temp_filename)
+
+########################################
+
+def validate_packages(suite, component, architecture):
+    filename = "%s/dists/%s/%s/binary-%s/Packages.gz" \
+               % (Cnf["Dir::Root"], suite, component, architecture)
+    print "Processing %s..." % (filename)
+    # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
+    temp_filename = utils.temp_filename()
+    (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
+    if (result != 0):
+        sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
+        sys.exit(result)
+    packages = utils.open_file(temp_filename)
+    Packages = apt_pkg.ParseTagFile(packages)
+    while Packages.Step():
+        filename = "%s/%s" % (Cnf["Dir::Root"], Packages.Section.Find('Filename'))
+        if not os.path.exists(filename):
+            print "W: %s missing." % (filename)
+    packages.close()
+    os.unlink(temp_filename)
+
+########################################
+
+def check_indices_files_exist():
+    for suite in [ "stable", "testing", "unstable" ]:
+        for component in Cnf.ValueList("Suite::%s::Components" % (suite)):
+            architectures = Cnf.ValueList("Suite::%s::Architectures" % (suite))
+            for arch in [ i.lower() for i in architectures ]:
+                if arch == "source":
+                    validate_sources(suite, component)
+                elif arch == "all":
+                    continue
+                else:
+                    validate_packages(suite, component, arch)
+
+################################################################################
+
+def check_files_not_symlinks():
+    print "Building list of database files... ",
+    before = time.time()
+    q = projectB.query("SELECT l.path, f.filename, f.id FROM files f, location l WHERE f.location = l.id")
+    print "done. (%d seconds)" % (int(time.time()-before))
+    q_files = q.getresult()
+
+    for i in q_files:
+        filename = os.path.normpath(i[0] + i[1])
+        if os.access(filename, os.R_OK) == 0:
+            utils.warn("%s: doesn't exist." % (filename))
+        else:
+            if os.path.islink(filename):
+                utils.warn("%s: is a symlink." % (filename))
+
+################################################################################
+
+def chk_bd_process_dir (unused, dirname, filenames):
+    for name in filenames:
+        if not name.endswith(".dsc"):
+            continue
+        filename = os.path.abspath(dirname+'/'+name)
+        dsc = utils.parse_changes(filename)
+        for field_name in [ "build-depends", "build-depends-indep" ]:
+            field = dsc.get(field_name)
+            if field:
+                try:
+                    apt_pkg.ParseSrcDepends(field)
+                except:
+                    print "E: [%s] %s: %s" % (filename, field_name, field)
+                    pass
+
+################################################################################
+
+def check_build_depends():
+    os.path.walk(Cnf["Dir::Root"], chk_bd_process_dir, None)
+
+################################################################################
+
+def main ():
+    global Cnf, projectB, db_files, waste, excluded
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Check-Archive::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Check-Archive::Options::%s" % (i)):
+            Cnf["Check-Archive::Options::%s" % (i)] = ""
+
+    args = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Check-Archive::Options")
+    if Options["Help"]:
+        usage()
+
+    if len(args) < 1:
+        utils.warn("dak check-archive requires at least one argument")
+        usage(1)
+    elif len(args) > 1:
+        utils.warn("dak check-archive accepts only one argument")
+        usage(1)
+    mode = args[0].lower()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    if mode == "checksums":
+        check_checksums()
+    elif mode == "files":
+        check_files()
+    elif mode == "dsc-syntax":
+        check_dscs()
+    elif mode == "missing-overrides":
+        check_override()
+    elif mode == "source-in-one-dir":
+        check_source_in_one_dir()
+    elif mode == "timestamps":
+        check_timestamps()
+    elif mode == "tar-gz-in-dsc":
+        check_missing_tar_gz_in_dsc()
+    elif mode == "validate-indices":
+        check_indices_files_exist()
+    elif mode == "files-not-symlinks":
+        check_files_not_symlinks()
+    elif mode == "validate-builddeps":
+        check_build_depends()
+    else:
+        utils.warn("unknown mode '%s'" % (mode))
+        usage(1)
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/check_overrides.py b/dak/check_overrides.py
new file mode 100644 (file)
index 0000000..f276dba
--- /dev/null
@@ -0,0 +1,352 @@
+#!/usr/bin/env python
+
+# Cruft checker and hole filler for overrides
+# Copyright (C) 2000, 2001, 2002, 2004, 2006  James Troup <james@nocrew.org>
+# Copyright (C) 2005  Jeroen van Wolffelaar <jeroen@wolffelaar.nl>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+######################################################################
+# NB: dak check-overrides is not a good idea with New Incoming as it #
+# doesn't take into account accepted.  You can minimize the impact   #
+# of this by running it immediately after dak process-accepted but   #
+# that's still racy because 'dak process-new' doesn't lock with 'dak #
+# process-accepted'.  A better long term fix is the evil plan for    #
+# accepted to be in the DB.                                          #
+######################################################################
+
+# dak check-overrides should now work fine being done during
+# cron.daily, for example just before 'dak make-overrides' (after 'dak
+# process-accepted' and 'dak make-suite-file-list'). At that point,
+# queue/accepted should be empty and installed, so... dak
+# check-overrides does now take into account suites sharing overrides
+
+# TODO:
+# * Only update out-of-sync overrides when corresponding versions are equal to
+#   some degree
+# * consistency checks like:
+#   - section=debian-installer only for udeb and # dsc
+#   - priority=source iff dsc
+#   - (suite, package, 'dsc') is unique,
+#   - just as (suite, package, (u)deb) (yes, across components!)
+#   - sections match their component (each component has an own set of sections,
+#     could probably be reduced...)
+
+################################################################################
+
+import pg, sys, os
+import apt_pkg
+from daklib import database
+from daklib import logging
+from daklib import utils
+
+################################################################################
+
+Options = None
+projectB = None
+Logger = None
+sections = {}
+priorities = {}
+blacklist = {}
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak check-overrides
+Check for cruft in overrides.
+
+  -n, --no-action            don't do anything
+  -h, --help                 show this help and exit"""
+
+    sys.exit(exit_code)
+
+################################################################################
+
+def gen_blacklist(dir):
+    for entry in os.listdir(dir):
+        entry = entry.split('_')[0]
+        blacklist[entry] = 1
+
+def process(osuite, affected_suites, originosuite, component, type):
+    global Logger, Options, projectB, sections, priorities
+
+    osuite_id = database.get_suite_id(osuite)
+    if osuite_id == -1:
+        utils.fubar("Suite '%s' not recognised." % (osuite))
+    originosuite_id = None
+    if originosuite:
+        originosuite_id = database.get_suite_id(originosuite)
+        if originosuite_id == -1:
+            utils.fubar("Suite '%s' not recognised." % (originosuite))
+
+    component_id = database.get_component_id(component)
+    if component_id == -1:
+        utils.fubar("Component '%s' not recognised." % (component))
+
+    type_id = database.get_override_type_id(type)
+    if type_id == -1:
+        utils.fubar("Type '%s' not recognised. (Valid types are deb, udeb and dsc)" % (type))
+    dsc_type_id = database.get_override_type_id("dsc")
+
+    source_priority_id = database.get_priority_id("source")
+
+    if type == "deb" or type == "udeb":
+        packages = {}
+        q = projectB.query("""
+SELECT b.package FROM binaries b, bin_associations ba, files f,
+                              location l, component c
+ WHERE b.type = '%s' AND b.id = ba.bin AND f.id = b.file AND l.id = f.location
+   AND c.id = l.component AND ba.suite IN (%s) AND c.id = %s
+""" % (type, ",".join([ str(i) for i in affected_suites ]), component_id))
+        for i in q.getresult():
+            packages[i[0]] = 0
+
+    src_packages = {}
+    q = projectB.query("""
+SELECT s.source FROM source s, src_associations sa, files f, location l,
+                     component c
+ WHERE s.id = sa.source AND f.id = s.file AND l.id = f.location
+   AND c.id = l.component AND sa.suite IN (%s) AND c.id = %s
+""" % (",".join([ str(i) for i in affected_suites]), component_id))
+    for i in q.getresult():
+        src_packages[i[0]] = 0
+
+    # -----------
+    # Drop unused overrides
+
+    q = projectB.query("SELECT package, priority, section, maintainer FROM override WHERE suite = %s AND component = %s AND type = %s" % (osuite_id, component_id, type_id))
+    projectB.query("BEGIN WORK")
+    if type == "dsc":
+        for i in q.getresult():
+            package = i[0]
+            if src_packages.has_key(package):
+                src_packages[package] = 1
+            else:
+                if blacklist.has_key(package):
+                    utils.warn("%s in incoming, not touching" % package)
+                    continue
+                Logger.log(["removing unused override", osuite, component,
+                    type, package, priorities[i[1]], sections[i[2]], i[3]])
+                if not Options["No-Action"]:
+                    projectB.query("""DELETE FROM override WHERE package =
+                        '%s' AND suite = %s AND component = %s AND type =
+                        %s""" % (package, osuite_id, component_id, type_id))
+        # create source overrides based on binary overrides, as source
+        # overrides not always get created
+        q = projectB.query(""" SELECT package, priority, section,
+            maintainer FROM override WHERE suite = %s AND component = %s
+            """ % (osuite_id, component_id))
+        for i in q.getresult():
+            package = i[0]
+            if not src_packages.has_key(package) or src_packages[package]:
+                continue
+            src_packages[package] = 1
+
+            Logger.log(["add missing override", osuite, component,
+                type, package, "source", sections[i[2]], i[3]])
+            if not Options["No-Action"]:
+                projectB.query("""INSERT INTO override (package, suite,
+                    component, priority, section, type, maintainer) VALUES
+                    ('%s', %s, %s, %s, %s, %s, '%s')""" % (package,
+                    osuite_id, component_id, source_priority_id, i[2],
+                    dsc_type_id, i[3]))
+        # Check whether originosuite has an override for us we can
+        # copy
+        if originosuite:
+            q = projectB.query("""SELECT origin.package, origin.priority,
+                origin.section, origin.maintainer, target.priority,
+                target.section, target.maintainer FROM override origin LEFT
+                JOIN override target ON (origin.package = target.package AND
+                target.suite=%s AND origin.component = target.component AND origin.type =
+                target.type) WHERE origin.suite = %s AND origin.component = %s
+                AND origin.type = %s""" %
+                (osuite_id, originosuite_id, component_id, type_id))
+            for i in q.getresult():
+                package = i[0]
+                if not src_packages.has_key(package) or src_packages[package]:
+                    if i[4] and (i[1] != i[4] or i[2] != i[5] or i[3] != i[6]):
+                        Logger.log(["syncing override", osuite, component,
+                            type, package, "source", sections[i[5]], i[6], "source", sections[i[2]], i[3]])
+                        if not Options["No-Action"]:
+                            projectB.query("""UPDATE override SET section=%s,
+                                maintainer='%s' WHERE package='%s' AND
+                                suite=%s AND component=%s AND type=%s""" %
+                                (i[2], i[3], package, osuite_id, component_id,
+                                dsc_type_id))
+                    continue
+                # we can copy
+                src_packages[package] = 1
+                Logger.log(["copying missing override", osuite, component,
+                    type, package, "source", sections[i[2]], i[3]])
+                if not Options["No-Action"]:
+                    projectB.query("""INSERT INTO override (package, suite,
+                        component, priority, section, type, maintainer) VALUES
+                        ('%s', %s, %s, %s, %s, %s, '%s')""" % (package,
+                        osuite_id, component_id, source_priority_id, i[2],
+                        dsc_type_id, i[3]))
+
+        for package, hasoverride in src_packages.items():
+            if not hasoverride:
+                utils.warn("%s has no override!" % package)
+
+    else: # binary override
+        for i in q.getresult():
+            package = i[0]
+            if packages.has_key(package):
+                packages[package] = 1
+            else:
+                if blacklist.has_key(package):
+                    utils.warn("%s in incoming, not touching" % package)
+                    continue
+                Logger.log(["removing unused override", osuite, component,
+                    type, package, priorities[i[1]], sections[i[2]], i[3]])
+                if not Options["No-Action"]:
+                    projectB.query("""DELETE FROM override WHERE package =
+                        '%s' AND suite = %s AND component = %s AND type =
+                        %s""" % (package, osuite_id, component_id, type_id))
+
+        # Check whether originosuite has an override for us we can
+        # copy
+        if originosuite:
+            q = projectB.query("""SELECT origin.package, origin.priority,
+                origin.section, origin.maintainer, target.priority,
+                target.section, target.maintainer FROM override origin LEFT
+                JOIN override target ON (origin.package = target.package AND
+                target.suite=%s AND origin.component = target.component AND
+                origin.type = target.type) WHERE origin.suite = %s AND
+                origin.component = %s AND origin.type = %s""" % (osuite_id,
+                originosuite_id, component_id, type_id))
+            for i in q.getresult():
+                package = i[0]
+                if not packages.has_key(package) or packages[package]:
+                    if i[4] and (i[1] != i[4] or i[2] != i[5] or i[3] != i[6]):
+                        Logger.log(["syncing override", osuite, component,
+                            type, package, priorities[i[4]], sections[i[5]],
+                            i[6], priorities[i[1]], sections[i[2]], i[3]])
+                        if not Options["No-Action"]:
+                            projectB.query("""UPDATE override SET priority=%s, section=%s,
+                                maintainer='%s' WHERE package='%s' AND
+                                suite=%s AND component=%s AND type=%s""" %
+                                (i[1], i[2], i[3], package, osuite_id,
+                                component_id, type_id))
+                    continue
+                # we can copy
+                packages[package] = 1
+                Logger.log(["copying missing override", osuite, component,
+                    type, package, priorities[i[1]], sections[i[2]], i[3]])
+                if not Options["No-Action"]:
+                    projectB.query("""INSERT INTO override (package, suite,
+                        component, priority, section, type, maintainer) VALUES
+                        ('%s', %s, %s, %s, %s, %s, '%s')""" % (package, osuite_id, component_id, i[1], i[2], type_id, i[3]))
+
+        for package, hasoverride in packages.items():
+            if not hasoverride:
+                utils.warn("%s has no override!" % package)
+
+    projectB.query("COMMIT WORK")
+    sys.stdout.flush()
+
+
+################################################################################
+
+def main ():
+    global Logger, Options, projectB, sections, priorities
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Check-Overrides::Options::Help"),
+                 ('n',"no-action", "Check-Overrides::Options::No-Action")]
+    for i in [ "help", "no-action" ]:
+        if not Cnf.has_key("Check-Overrides::Options::%s" % (i)):
+            Cnf["Check-Overrides::Options::%s" % (i)] = ""
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+    Options = Cnf.SubTree("Check-Overrides::Options")
+
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    # init sections, priorities:
+    q = projectB.query("SELECT id, section FROM section")
+    for i in q.getresult():
+        sections[i[0]] = i[1]
+    q = projectB.query("SELECT id, priority FROM priority")
+    for i in q.getresult():
+        priorities[i[0]] = i[1]
+
+    if not Options["No-Action"]:
+        Logger = logging.Logger(Cnf, "check-overrides")
+    else:
+        Logger = logging.Logger(Cnf, "check-overrides", 1)
+
+    gen_blacklist(Cnf["Dir::Queue::Accepted"])
+
+    for osuite in Cnf.SubTree("Check-Overrides::OverrideSuites").List():
+        if "1" != Cnf["Check-Overrides::OverrideSuites::%s::Process" % osuite]:
+            continue
+
+        osuite = osuite.lower()
+
+        originosuite = None
+        originremark = ""
+        try:
+            originosuite = Cnf["Check-Overrides::OverrideSuites::%s::OriginSuite" % osuite]
+            originosuite = originosuite.lower()
+            originremark = " taking missing from %s" % originosuite
+        except KeyError:
+            pass
+
+        print "Processing %s%s..." % (osuite, originremark)
+        # Get a list of all suites that use the override file of 'osuite'
+        ocodename = Cnf["Suite::%s::codename" % osuite].lower()
+        suites = []
+        for suite in Cnf.SubTree("Suite").List():
+            if ocodename == Cnf["Suite::%s::OverrideCodeName" % suite].lower():
+                suites.append(suite)
+
+        q = projectB.query("SELECT id FROM suite WHERE suite_name in (%s)" \
+            % ", ".join([ repr(i) for i in suites ]).lower())
+
+        suiteids = []
+        for i in q.getresult():
+            suiteids.append(i[0])
+
+        if len(suiteids) != len(suites) or len(suiteids) < 1:
+            utils.fubar("Couldn't find id's of all suites: %s" % suites)
+
+        for component in Cnf.SubTree("Component").List():
+            if component == "mixed":
+                continue; # Ick
+            # It is crucial for the dsc override creation based on binary
+            # overrides that 'dsc' goes first
+            otypes = Cnf.ValueList("OverrideType")
+            otypes.remove("dsc")
+            otypes = ["dsc"] + otypes
+            for otype in otypes:
+                print "Processing %s [%s - %s] using %s..." \
+                    % (osuite, component, otype, suites)
+                sys.stdout.flush()
+                process(osuite, suiteids, originosuite, component, otype)
+
+    Logger.close()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/check_proposed_updates.py b/dak/check_proposed_updates.py
new file mode 100755 (executable)
index 0000000..afb0faa
--- /dev/null
@@ -0,0 +1,308 @@
+#!/usr/bin/env python
+
+# Dependency check proposed-updates
+# Copyright (C) 2001, 2002, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# | > amd64 is more mature than even some released architectures
+# |
+# | This might be true of the architecture, unfortunately it seems to be the
+# | exact opposite for most of the people involved with it.
+#
+# <1089213290.24029.6.camel@descent.netsplit.com>
+
+################################################################################
+
+import pg, sys, os
+import apt_pkg, apt_inst
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+Options = None
+stable = {}
+stable_virtual = {}
+architectures = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak check-proposed-updates [OPTION] <CHANGES FILE | DEB FILE | ADMIN FILE>[...]
+(Very) Basic dependency checking for proposed-updates.
+
+  -q, --quiet                be quieter about what is being done
+  -v, --verbose              be more verbose about what is being done
+  -h, --help                 show this help and exit
+
+Need either changes files, deb files or an admin.txt file with a '.joey' suffix."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def d_test (dict, key, positive, negative):
+    if not dict:
+        return negative
+    if dict.has_key(key):
+        return positive
+    else:
+        return negative
+
+################################################################################
+
+def check_dep (depends, dep_type, check_archs, filename, files):
+    pkg_unsat = 0
+    for arch in check_archs:
+        for parsed_dep in apt_pkg.ParseDepends(depends):
+            unsat = []
+            for atom in parsed_dep:
+                (dep, version, constraint) = atom
+                # As a real package?
+                if stable.has_key(dep):
+                    if stable[dep].has_key(arch):
+                        if apt_pkg.CheckDep(stable[dep][arch], constraint, version):
+                            if Options["debug"]:
+                                print "Found %s as a real package." % (utils.pp_deps(parsed_dep))
+                            unsat = 0
+                            break
+                # As a virtual?
+                if stable_virtual.has_key(dep):
+                    if stable_virtual[dep].has_key(arch):
+                        if not constraint and not version:
+                            if Options["debug"]:
+                                print "Found %s as a virtual package." % (utils.pp_deps(parsed_dep))
+                            unsat = 0
+                            break
+                # As part of the same .changes?
+                epochless_version = utils.re_no_epoch.sub('', version)
+                dep_filename = "%s_%s_%s.deb" % (dep, epochless_version, arch)
+                if files.has_key(dep_filename):
+                    if Options["debug"]:
+                        print "Found %s in the same upload." % (utils.pp_deps(parsed_dep))
+                    unsat = 0
+                    break
+                # Not found...
+                # [FIXME: must be a better way ... ]
+                error = "%s not found. [Real: " % (utils.pp_deps(parsed_dep))
+                if stable.has_key(dep):
+                    if stable[dep].has_key(arch):
+                        error += "%s:%s:%s" % (dep, arch, stable[dep][arch])
+                    else:
+                        error += "%s:-:-" % (dep)
+                else:
+                    error += "-:-:-"
+                error += ", Virtual: "
+                if stable_virtual.has_key(dep):
+                    if stable_virtual[dep].has_key(arch):
+                        error += "%s:%s" % (dep, arch)
+                    else:
+                        error += "%s:-"
+                else:
+                    error += "-:-"
+                error += ", Upload: "
+                if files.has_key(dep_filename):
+                    error += "yes"
+                else:
+                    error += "no"
+                error += "]"
+                unsat.append(error)
+
+            if unsat:
+                sys.stderr.write("MWAAP! %s: '%s' %s can not be satisifed:\n" % (filename, utils.pp_deps(parsed_dep), dep_type))
+                for error in unsat:
+                    sys.stderr.write("  %s\n" % (error))
+                pkg_unsat = 1
+
+    return pkg_unsat
+
+def check_package(filename, files):
+    try:
+        control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(filename)))
+    except:
+        utils.warn("%s: debExtractControl() raised %s." % (filename, sys.exc_type))
+        return 1
+    Depends = control.Find("Depends")
+    Pre_Depends = control.Find("Pre-Depends")
+    #Recommends = control.Find("Recommends")
+    pkg_arch = control.Find("Architecture")
+    base_file = os.path.basename(filename)
+    if pkg_arch == "all":
+        check_archs = architectures
+    else:
+        check_archs = [pkg_arch]
+
+    pkg_unsat = 0
+    if Pre_Depends:
+        pkg_unsat += check_dep(Pre_Depends, "pre-dependency", check_archs, base_file, files)
+
+    if Depends:
+        pkg_unsat += check_dep(Depends, "dependency", check_archs, base_file, files)
+    #if Recommends:
+    #pkg_unsat += check_dep(Recommends, "recommendation", check_archs, base_file, files)
+
+    return pkg_unsat
+
+################################################################################
+
+def pass_fail (filename, result):
+    if not Options["quiet"]:
+        print "%s:" % (os.path.basename(filename)),
+        if result:
+            print "FAIL"
+        else:
+            print "ok"
+
+################################################################################
+
+def check_changes (filename):
+    try:
+        changes = utils.parse_changes(filename)
+        files = utils.build_file_list(changes)
+    except:
+        utils.warn("Error parsing changes file '%s'" % (filename))
+        return
+
+    result = 0
+
+    # Move to the pool directory
+    cwd = os.getcwd()
+    f = files.keys()[0]
+    pool_dir = Cnf["Dir::Pool"] + '/' + utils.poolify(changes["source"], files[f]["component"])
+    os.chdir(pool_dir)
+
+    changes_result = 0
+    for f in files.keys():
+        if f.endswith(".deb"):
+            result = check_package(f, files)
+            if Options["verbose"]:
+                pass_fail(f, result)
+            changes_result += result
+
+    pass_fail (filename, changes_result)
+
+    # Move back
+    os.chdir(cwd)
+
+################################################################################
+
+def check_deb (filename):
+    result = check_package(filename, {})
+    pass_fail(filename, result)
+
+
+################################################################################
+
+def check_joey (filename):
+    f = utils.open_file(filename)
+
+    cwd = os.getcwd()
+    os.chdir("%s/dists/proposed-updates" % (Cnf["Dir::Root"]))
+
+    for line in f.readlines():
+        line = line.rstrip()
+        if line.find('install') != -1:
+            split_line = line.split()
+            if len(split_line) != 2:
+                utils.fubar("Parse error (not exactly 2 elements): %s" % (line))
+            install_type = split_line[0]
+            if install_type not in [ "install", "install-u", "sync-install" ]:
+                utils.fubar("Unknown install type ('%s') from: %s" % (install_type, line))
+            changes_filename = split_line[1]
+            if Options["debug"]:
+                print "Processing %s..." % (changes_filename)
+            check_changes(changes_filename)
+    f.close()
+
+    os.chdir(cwd)
+
+################################################################################
+
+def parse_packages():
+    global stable, stable_virtual, architectures
+
+    # Parse the Packages files (since it's a sub-second operation on auric)
+    suite = "stable"
+    stable = {}
+    components = Cnf.ValueList("Suite::%s::Components" % (suite))
+    architectures = filter(utils.real_arch, Cnf.ValueList("Suite::%s::Architectures" % (suite)))
+    for component in components:
+        for architecture in architectures:
+            filename = "%s/dists/%s/%s/binary-%s/Packages" % (Cnf["Dir::Root"], suite, component, architecture)
+            packages = utils.open_file(filename, 'r')
+            Packages = apt_pkg.ParseTagFile(packages)
+            while Packages.Step():
+                package = Packages.Section.Find('Package')
+                version = Packages.Section.Find('Version')
+                provides = Packages.Section.Find('Provides')
+                if not stable.has_key(package):
+                    stable[package] = {}
+                stable[package][architecture] = version
+                if provides:
+                    for virtual_pkg in provides.split(","):
+                        virtual_pkg = virtual_pkg.strip()
+                        if not stable_virtual.has_key(virtual_pkg):
+                            stable_virtual[virtual_pkg] = {}
+                        stable_virtual[virtual_pkg][architecture] = "NA"
+            packages.close()
+
+################################################################################
+
+def main ():
+    global Cnf, projectB, Options
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('d', "debug", "Check-Proposed-Updates::Options::Debug"),
+                 ('q',"quiet","Check-Proposed-Updates::Options::Quiet"),
+                 ('v',"verbose","Check-Proposed-Updates::Options::Verbose"),
+                 ('h',"help","Check-Proposed-Updates::Options::Help")]
+    for i in [ "debug", "quiet", "verbose", "help" ]:
+        if not Cnf.has_key("Check-Proposed-Updates::Options::%s" % (i)):
+            Cnf["Check-Proposed-Updates::Options::%s" % (i)] = ""
+
+    arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Check-Proposed-Updates::Options")
+
+    if Options["Help"]:
+        usage(0)
+    if not arguments:
+        utils.fubar("need at least one package name as an argument.")
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    print "Parsing packages files...",
+    parse_packages()
+    print "done."
+
+    for f in arguments:
+        if f.endswith(".changes"):
+            check_changes(f)
+        elif f.endswith(".deb"):
+            check_deb(f)
+        elif f.endswith(".joey"):
+            check_joey(f)
+        else:
+            utils.fubar("Unrecognised file type: '%s'." % (f))
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/clean_proposed_updates.py b/dak/clean_proposed_updates.py
new file mode 100755 (executable)
index 0000000..3dd6e6f
--- /dev/null
@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+
+# Remove obsolete .changes files from proposed-updates
+# Copyright (C) 2001, 2002, 2003, 2004, 2006, 2008  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, pg, re, sys
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+Options = None
+pu = {}
+
+re_isdeb = re.compile (r"^(.+)_(.+?)_(.+?).u?deb$")
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak clean-proposed-updates [OPTION] <CHANGES FILE | ADMIN FILE>[...]
+Remove obsolete changes files from proposed-updates.
+
+  -v, --verbose              be more verbose about what is being done
+  -h, --help                 show this help and exit
+
+Need either changes files or an admin.txt file with a '.joey' suffix."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def check_changes (filename):
+    try:
+        changes = utils.parse_changes(filename)
+        files = utils.build_file_list(changes)
+    except:
+        utils.warn("Couldn't read changes file '%s'." % (filename))
+        return
+    num_files = len(files.keys())
+    for f in files.keys():
+        if utils.re_isadeb.match(f):
+            m = re_isdeb.match(f)
+            pkg = m.group(1)
+            version = m.group(2)
+            arch = m.group(3)
+            if Options["debug"]:
+                print "BINARY: %s ==> %s_%s_%s" % (f, pkg, version, arch)
+        else:
+            m = utils.re_issource.match(f)
+            if m:
+                pkg = m.group(1)
+                version = m.group(2)
+                ftype = m.group(3)
+                if ftype != "dsc":
+                    del files[f]
+                    num_files -= 1
+                    continue
+                arch = "source"
+                if Options["debug"]:
+                    print "SOURCE: %s ==> %s_%s_%s" % (f, pkg, version, arch)
+            else:
+                utils.fubar("unknown type, fix me")
+        if not pu.has_key(pkg):
+            # FIXME
+            utils.warn("%s doesn't seem to exist in %s?? (from %s [%s])" % (pkg, Options["suite"], f, filename))
+            continue
+        if not pu[pkg].has_key(arch):
+            # FIXME
+            utils.warn("%s doesn't seem to exist for %s in %s?? (from %s [%s])" % (pkg, arch, Options["suite"], f, filename))
+            continue
+        pu_version = utils.re_no_epoch.sub('', pu[pkg][arch])
+        if pu_version == version:
+            if Options["verbose"]:
+                print "%s: ok" % (f)
+        else:
+            if Options["verbose"]:
+                print "%s: superseded, removing. [%s]" % (f, pu_version)
+            del files[f]
+
+    new_num_files = len(files.keys())
+    if new_num_files == 0:
+        print "%s: no files left, superseded by %s" % (filename, pu_version)
+        dest = Cnf["Dir::Morgue"] + "/misc/"
+        if not Options["no-action"]:
+            utils.move(filename, dest)
+    elif new_num_files < num_files:
+        print "%s: lost files, MWAAP." % (filename)
+    else:
+        if Options["verbose"]:
+            print "%s: ok" % (filename)
+
+################################################################################
+
+def check_joey (filename):
+    f = utils.open_file(filename)
+
+    cwd = os.getcwd()
+    os.chdir("%s/dists/%s" % (Cnf["Dir::Root"]), Options["suite"])
+
+    for line in f.readlines():
+        line = line.rstrip()
+        if line.find('install') != -1:
+            split_line = line.split()
+            if len(split_line) != 2:
+                utils.fubar("Parse error (not exactly 2 elements): %s" % (line))
+            install_type = split_line[0]
+            if install_type not in [ "install", "install-u", "sync-install" ]:
+                utils.fubar("Unknown install type ('%s') from: %s" % (install_type, line))
+            changes_filename = split_line[1]
+            if Options["debug"]:
+                print "Processing %s..." % (changes_filename)
+            check_changes(changes_filename)
+
+    os.chdir(cwd)
+
+################################################################################
+
+def init_pu ():
+    global pu
+
+    q = projectB.query("""
+SELECT b.package, b.version, a.arch_string
+  FROM bin_associations ba, binaries b, suite su, architecture a
+  WHERE b.id = ba.bin AND ba.suite = su.id
+    AND su.suite_name = '%s' AND a.id = b.architecture
+UNION SELECT s.source, s.version, 'source'
+  FROM src_associations sa, source s, suite su
+  WHERE s.id = sa.source AND sa.suite = su.id
+    AND su.suite_name = '%s'
+ORDER BY package, version, arch_string
+""" % (Options["suite"], Options["suite"]))
+    ql = q.getresult()
+    for i in ql:
+        pkg = i[0]
+        version = i[1]
+        arch = i[2]
+        if not pu.has_key(pkg):
+            pu[pkg] = {}
+        pu[pkg][arch] = version
+
+def main ():
+    global Cnf, projectB, Options
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('d', "debug", "Clean-Proposed-Updates::Options::Debug"),
+                 ('v', "verbose", "Clean-Proposed-Updates::Options::Verbose"),
+                 ('h', "help", "Clean-Proposed-Updates::Options::Help"),
+                 ('s', "suite", "Clean-Proposed-Updates::Options::Suite", "HasArg"),
+                 ('n', "no-action", "Clean-Proposed-Updates::Options::No-Action"),]
+    for i in [ "debug", "verbose", "help", "no-action" ]:
+        if not Cnf.has_key("Clean-Proposed-Updates::Options::%s" % (i)):
+            Cnf["Clean-Proposed-Updates::Options::%s" % (i)] = ""
+
+    # suite defaults to proposed-updates
+    if not Cnf.has_key("Clean-Proposed-Updates::Options::Suite"):
+        Cnf["Clean-Proposed-Updates::Options::Suite"] = "proposed-updates"
+
+    arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Clean-Proposed-Updates::Options")
+
+    if Options["Help"]:
+        usage(0)
+    if not arguments:
+        utils.fubar("need at least one package name as an argument.")
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    init_pu()
+
+    for f in arguments:
+        if f.endswith(".changes"):
+            check_changes(f)
+        elif f.endswith(".joey"):
+            check_joey(f)
+        else:
+            utils.fubar("Unrecognised file type: '%s'." % (f))
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/clean_queues.py b/dak/clean_queues.py
new file mode 100755 (executable)
index 0000000..9f771b7
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+
+# Clean incoming of old unused files
+# Copyright (C) 2000, 2001, 2002, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <aj> Bdale, a ham-er, and the leader,
+# <aj> Willy, a GCC maintainer,
+# <aj> Lamont-work, 'cause he's the top uploader....
+# <aj>         Penguin Puff' save the day!
+# <aj> Porting code, trying to build the world,
+# <aj> Here they come just in time...
+# <aj>         The Penguin Puff' Guys!
+# <aj> [repeat]
+# <aj> Penguin Puff'!
+# <aj> willy: btw, if you don't maintain gcc you need to start, since
+#      the lyrics fit really well that way
+
+################################################################################
+
+import os, stat, sys, time
+import apt_pkg
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+Options = None
+del_dir = None
+delete_date = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak clean-queues [OPTIONS]
+Clean out incoming directories.
+
+  -d, --days=DAYS            remove anything older than DAYS old
+  -i, --incoming=INCOMING    the incoming directory to clean
+  -n, --no-action            don't do anything
+  -v, --verbose              explain what is being done
+  -h, --help                 show this help and exit"""
+
+    sys.exit(exit_code)
+
+################################################################################
+
+def init ():
+    global delete_date, del_dir
+
+    delete_date = int(time.time())-(int(Options["Days"])*84600)
+
+    # Ensure a directory exists to remove files to
+    if not Options["No-Action"]:
+        date = time.strftime("%Y-%m-%d")
+        del_dir = Cnf["Dir::Morgue"] + '/' + Cnf["Clean-Queues::MorgueSubDir"] + '/' + date
+        if not os.path.exists(del_dir):
+            os.makedirs(del_dir, 02775)
+        if not os.path.isdir(del_dir):
+            utils.fubar("%s must be a directory." % (del_dir))
+
+    # Move to the directory to clean
+    incoming = Options["Incoming"]
+    if incoming == "":
+        incoming = Cnf["Dir::Queue::Unchecked"]
+    os.chdir(incoming)
+
+# Remove a file to the morgue
+def remove (f):
+    if os.access(f, os.R_OK):
+        dest_filename = del_dir + '/' + os.path.basename(f)
+        # If the destination file exists; try to find another filename to use
+        if os.path.exists(dest_filename):
+            dest_filename = utils.find_next_free(dest_filename, 10)
+        utils.move(f, dest_filename, 0660)
+    else:
+        utils.warn("skipping '%s', permission denied." % (os.path.basename(f)))
+
+# Removes any old files.
+# [Used for Incoming/REJECT]
+#
+def flush_old ():
+    for f in os.listdir('.'):
+        if os.path.isfile(f):
+            if os.stat(f)[stat.ST_MTIME] < delete_date:
+                if Options["No-Action"]:
+                    print "I: Would delete '%s'." % (os.path.basename(f))
+                else:
+                    if Options["Verbose"]:
+                        print "Removing '%s' (to '%s')."  % (os.path.basename(f), del_dir)
+                    remove(f)
+            else:
+                if Options["Verbose"]:
+                    print "Skipping, too new, '%s'." % (os.path.basename(f))
+
+# Removes any files which are old orphans (not associated with a valid .changes file).
+# [Used for Incoming]
+#
+def flush_orphans ():
+    all_files = {}
+    changes_files = []
+
+    # Build up the list of all files in the directory
+    for i in os.listdir('.'):
+        if os.path.isfile(i):
+            all_files[i] = 1
+            if i.endswith(".changes"):
+                changes_files.append(i)
+
+    # Proces all .changes and .dsc files.
+    for changes_filename in changes_files:
+        try:
+            changes = utils.parse_changes(changes_filename)
+            files = utils.build_file_list(changes)
+        except:
+            utils.warn("error processing '%s'; skipping it. [Got %s]" % (changes_filename, sys.exc_type))
+            continue
+
+        dsc_files = {}
+        for f in files.keys():
+            if f.endswith(".dsc"):
+                try:
+                    dsc = utils.parse_changes(f)
+                    dsc_files = utils.build_file_list(dsc, is_a_dsc=1)
+                except:
+                    utils.warn("error processing '%s'; skipping it. [Got %s]" % (f, sys.exc_type))
+                    continue
+
+        # Ensure all the files we've seen aren't deleted
+        keys = []
+        for i in (files.keys(), dsc_files.keys(), [changes_filename]):
+            keys.extend(i)
+        for key in keys:
+            if all_files.has_key(key):
+                if Options["Verbose"]:
+                    print "Skipping, has parents, '%s'." % (key)
+                del all_files[key]
+
+    # Anthing left at this stage is not referenced by a .changes (or
+    # a .dsc) and should be deleted if old enough.
+    for f in all_files.keys():
+        if os.stat(f)[stat.ST_MTIME] < delete_date:
+            if Options["No-Action"]:
+                print "I: Would delete '%s'." % (os.path.basename(f))
+            else:
+                if Options["Verbose"]:
+                    print "Removing '%s' (to '%s')."  % (os.path.basename(f), del_dir)
+                remove(f)
+        else:
+            if Options["Verbose"]:
+                print "Skipping, too new, '%s'." % (os.path.basename(f))
+
+################################################################################
+
+def main ():
+    global Cnf, Options
+
+    Cnf = utils.get_conf()
+
+    for i in ["Help", "Incoming", "No-Action", "Verbose" ]:
+        if not Cnf.has_key("Clean-Queues::Options::%s" % (i)):
+            Cnf["Clean-Queues::Options::%s" % (i)] = ""
+    if not Cnf.has_key("Clean-Queues::Options::Days"):
+        Cnf["Clean-Queues::Options::Days"] = "14"
+
+    Arguments = [('h',"help","Clean-Queues::Options::Help"),
+                 ('d',"days","Clean-Queues::Options::Days", "IntLevel"),
+                 ('i',"incoming","Clean-Queues::Options::Incoming", "HasArg"),
+                 ('n',"no-action","Clean-Queues::Options::No-Action"),
+                 ('v',"verbose","Clean-Queues::Options::Verbose")]
+
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Clean-Queues::Options")
+
+    if Options["Help"]:
+        usage()
+
+    init()
+
+    if Options["Verbose"]:
+        print "Processing incoming..."
+    flush_orphans()
+
+    reject = Cnf["Dir::Queue::Reject"]
+    if os.path.exists(reject) and os.path.isdir(reject):
+        if Options["Verbose"]:
+            print "Processing incoming/REJECT..."
+        os.chdir(reject)
+        flush_old()
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/clean_suites.py b/dak/clean_suites.py
new file mode 100755 (executable)
index 0000000..fc4b847
--- /dev/null
@@ -0,0 +1,357 @@
+#!/usr/bin/env python
+
+# Cleans up unassociated binary and source packages
+# Copyright (C) 2000, 2001, 2002, 2003, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# 07:05|<elmo> well.. *shrug*.. no, probably not.. but to fix it,
+#      |       we're going to have to implement reference counting
+#      |       through dependencies.. do we really want to go down
+#      |       that road?
+#
+# 07:05|<Culus> elmo: Augh! <brain jumps out of skull>
+
+################################################################################
+
+import os, pg, stat, sys, time
+import apt_pkg
+from daklib import utils
+
+################################################################################
+
+projectB = None
+Cnf = None
+Options = None
+now_date = None;     # mark newly "deleted" things as deleted "now"
+delete_date = None;  # delete things marked "deleted" earler than this
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak clean-suites [OPTIONS]
+Clean old packages from suites.
+
+  -n, --no-action            don't do anything
+  -h, --help                 show this help and exit"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def check_binaries():
+    global delete_date, now_date
+
+    print "Checking for orphaned binary packages..."
+
+    # Get the list of binary packages not in a suite and mark them for
+    # deletion.
+    q = projectB.query("""
+SELECT b.file FROM binaries b, files f
+ WHERE f.last_used IS NULL AND b.file = f.id
+   AND NOT EXISTS (SELECT 1 FROM bin_associations ba WHERE ba.bin = b.id)""")
+    ql = q.getresult()
+
+    projectB.query("BEGIN WORK")
+    for i in ql:
+        file_id = i[0]
+        projectB.query("UPDATE files SET last_used = '%s' WHERE id = %s AND last_used IS NULL" % (now_date, file_id))
+    projectB.query("COMMIT WORK")
+
+    # Check for any binaries which are marked for eventual deletion
+    # but are now used again.
+    q = projectB.query("""
+SELECT b.file FROM binaries b, files f
+   WHERE f.last_used IS NOT NULL AND f.id = b.file
+    AND EXISTS (SELECT 1 FROM bin_associations ba WHERE ba.bin = b.id)""")
+    ql = q.getresult()
+
+    projectB.query("BEGIN WORK")
+    for i in ql:
+        file_id = i[0]
+        projectB.query("UPDATE files SET last_used = NULL WHERE id = %s" % (file_id))
+    projectB.query("COMMIT WORK")
+
+########################################
+
+def check_sources():
+    global delete_date, now_date
+
+    print "Checking for orphaned source packages..."
+
+    # Get the list of source packages not in a suite and not used by
+    # any binaries.
+    q = projectB.query("""
+SELECT s.id, s.file FROM source s, files f
+  WHERE f.last_used IS NULL AND s.file = f.id
+    AND NOT EXISTS (SELECT 1 FROM src_associations sa WHERE sa.source = s.id)
+    AND NOT EXISTS (SELECT 1 FROM binaries b WHERE b.source = s.id)""")
+
+    #### XXX: this should ignore cases where the files for the binary b
+    ####      have been marked for deletion (so the delay between bins go
+    ####      byebye and sources go byebye is 0 instead of StayOfExecution)
+
+    ql = q.getresult()
+
+    projectB.query("BEGIN WORK")
+    for i in ql:
+        source_id = i[0]
+        dsc_file_id = i[1]
+
+        # Mark the .dsc file for deletion
+        projectB.query("UPDATE files SET last_used = '%s' WHERE id = %s AND last_used IS NULL" % (now_date, dsc_file_id))
+        # Mark all other files references by .dsc too if they're not used by anyone else
+        x = projectB.query("SELECT f.id FROM files f, dsc_files d WHERE d.source = %s AND d.file = f.id" % (source_id))
+        for j in x.getresult():
+            file_id = j[0]
+            y = projectB.query("SELECT id FROM dsc_files d WHERE d.file = %s" % (file_id))
+            if len(y.getresult()) == 1:
+                projectB.query("UPDATE files SET last_used = '%s' WHERE id = %s AND last_used IS NULL" % (now_date, file_id))
+    projectB.query("COMMIT WORK")
+
+    # Check for any sources which are marked for deletion but which
+    # are now used again.
+
+    q = projectB.query("""
+SELECT f.id FROM source s, files f, dsc_files df
+  WHERE f.last_used IS NOT NULL AND s.id = df.source AND df.file = f.id
+    AND ((EXISTS (SELECT 1 FROM src_associations sa WHERE sa.source = s.id))
+      OR (EXISTS (SELECT 1 FROM binaries b WHERE b.source = s.id)))""")
+
+    #### XXX: this should also handle deleted binaries specially (ie, not
+    ####      reinstate sources because of them
+
+    ql = q.getresult()
+    # Could be done in SQL; but left this way for hysterical raisins
+    # [and freedom to innovate don'cha know?]
+    projectB.query("BEGIN WORK")
+    for i in ql:
+        file_id = i[0]
+        projectB.query("UPDATE files SET last_used = NULL WHERE id = %s" % (file_id))
+    projectB.query("COMMIT WORK")
+
+########################################
+
+def check_files():
+    global delete_date, now_date
+
+    # FIXME: this is evil; nothing should ever be in this state.  if
+    # they are, it's a bug and the files should not be auto-deleted.
+
+    return
+
+    print "Checking for unused files..."
+    q = projectB.query("""
+SELECT id FROM files f
+  WHERE NOT EXISTS (SELECT 1 FROM binaries b WHERE b.file = f.id)
+    AND NOT EXISTS (SELECT 1 FROM dsc_files df WHERE df.file = f.id)""")
+
+    projectB.query("BEGIN WORK")
+    for i in q.getresult():
+        file_id = i[0]
+        projectB.query("UPDATE files SET last_used = '%s' WHERE id = %s" % (now_date, file_id))
+    projectB.query("COMMIT WORK")
+
+def clean_binaries():
+    global delete_date, now_date
+
+    # We do this here so that the binaries we remove will have their
+    # source also removed (if possible).
+
+    # XXX: why doesn't this remove the files here as well? I don't think it
+    #      buys anything keeping this separate
+    print "Cleaning binaries from the DB..."
+    if not Options["No-Action"]:
+        before = time.time()
+        sys.stdout.write("[Deleting from binaries table... ")
+        projectB.query("DELETE FROM binaries WHERE EXISTS (SELECT 1 FROM files WHERE binaries.file = files.id AND files.last_used <= '%s')" % (delete_date))
+        sys.stdout.write("done. (%d seconds)]\n" % (int(time.time()-before)))
+
+########################################
+
+def clean():
+    global delete_date, now_date
+    count = 0
+    size = 0
+
+    print "Cleaning out packages..."
+
+    date = time.strftime("%Y-%m-%d")
+    dest = Cnf["Dir::Morgue"] + '/' + Cnf["Clean-Suites::MorgueSubDir"] + '/' + date
+    if not os.path.exists(dest):
+        os.mkdir(dest)
+
+    # Delete from source
+    if not Options["No-Action"]:
+        before = time.time()
+        sys.stdout.write("[Deleting from source table... ")
+        projectB.query("DELETE FROM dsc_files WHERE EXISTS (SELECT 1 FROM source s, files f, dsc_files df WHERE f.last_used <= '%s' AND s.file = f.id AND s.id = df.source AND df.id = dsc_files.id)" % (delete_date))
+        projectB.query("DELETE FROM src_uploaders WHERE EXISTS (SELECT 1 FROM source s, files f WHERE f.last_used <= '%s' AND s.file = f.id AND s.id = src_uploaders.source)" % (delete_date))
+        projectB.query("DELETE FROM source WHERE EXISTS (SELECT 1 FROM files WHERE source.file = files.id AND files.last_used <= '%s')" % (delete_date))
+        sys.stdout.write("done. (%d seconds)]\n" % (int(time.time()-before)))
+
+    # Delete files from the pool
+    q = projectB.query("SELECT l.path, f.filename FROM location l, files f WHERE f.last_used <= '%s' AND l.id = f.location" % (delete_date))
+    for i in q.getresult():
+        filename = i[0] + i[1]
+        if not os.path.exists(filename):
+            utils.warn("can not find '%s'." % (filename))
+            continue
+        if os.path.isfile(filename):
+            if os.path.islink(filename):
+                count += 1
+                if Options["No-Action"]:
+                    print "Removing symlink %s..." % (filename)
+                else:
+                    os.unlink(filename)
+            else:
+                size += os.stat(filename)[stat.ST_SIZE]
+                count += 1
+
+                dest_filename = dest + '/' + os.path.basename(filename)
+                # If the destination file exists; try to find another filename to use
+                if os.path.exists(dest_filename):
+                    dest_filename = utils.find_next_free(dest_filename)
+
+                if Options["No-Action"]:
+                    print "Cleaning %s -> %s ..." % (filename, dest_filename)
+                else:
+                    utils.move(filename, dest_filename)
+        else:
+            utils.fubar("%s is neither symlink nor file?!" % (filename))
+
+    # Delete from the 'files' table
+    if not Options["No-Action"]:
+        before = time.time()
+        sys.stdout.write("[Deleting from files table... ")
+        projectB.query("DELETE FROM files WHERE last_used <= '%s'" % (delete_date))
+        sys.stdout.write("done. (%d seconds)]\n" % (int(time.time()-before)))
+    if count > 0:
+        sys.stderr.write("Cleaned %d files, %s.\n" % (count, utils.size_type(size)))
+
+################################################################################
+
+def clean_maintainers():
+    print "Cleaning out unused Maintainer entries..."
+
+    q = projectB.query("""
+SELECT m.id FROM maintainer m
+  WHERE NOT EXISTS (SELECT 1 FROM binaries b WHERE b.maintainer = m.id)
+    AND NOT EXISTS (SELECT 1 FROM source s WHERE s.maintainer = m.id OR s.changedby = m.id)
+    AND NOT EXISTS (SELECT 1 FROM src_uploaders u WHERE u.maintainer = m.id)""")
+    ql = q.getresult()
+
+    count = 0
+    projectB.query("BEGIN WORK")
+    for i in ql:
+        maintainer_id = i[0]
+        if not Options["No-Action"]:
+            projectB.query("DELETE FROM maintainer WHERE id = %s" % (maintainer_id))
+            count += 1
+    projectB.query("COMMIT WORK")
+
+    if count > 0:
+        sys.stderr.write("Cleared out %d maintainer entries.\n" % (count))
+
+################################################################################
+
+def clean_fingerprints():
+    print "Cleaning out unused fingerprint entries..."
+
+    q = projectB.query("""
+SELECT f.id FROM fingerprint f
+  WHERE f.keyring IS NULL
+    AND NOT EXISTS (SELECT 1 FROM binaries b WHERE b.sig_fpr = f.id)
+    AND NOT EXISTS (SELECT 1 FROM source s WHERE s.sig_fpr = f.id)""")
+    ql = q.getresult()
+
+    count = 0
+    projectB.query("BEGIN WORK")
+    for i in ql:
+        fingerprint_id = i[0]
+        if not Options["No-Action"]:
+            projectB.query("DELETE FROM fingerprint WHERE id = %s" % (fingerprint_id))
+            count += 1
+    projectB.query("COMMIT WORK")
+
+    if count > 0:
+        sys.stderr.write("Cleared out %d fingerprint entries.\n" % (count))
+
+################################################################################
+
+def clean_queue_build():
+    global now_date
+
+    if not Cnf.ValueList("Dinstall::QueueBuildSuites") or Options["No-Action"]:
+        return
+
+    print "Cleaning out queue build symlinks..."
+
+    our_delete_date = time.strftime("%Y-%m-%d %H:%M", time.localtime(time.time()-int(Cnf["Clean-Suites::QueueBuildStayOfExecution"])))
+    count = 0
+
+    q = projectB.query("SELECT filename FROM queue_build WHERE last_used <= '%s'" % (our_delete_date))
+    for i in q.getresult():
+        filename = i[0]
+        if not os.path.exists(filename):
+            utils.warn("%s (from queue_build) doesn't exist." % (filename))
+            continue
+        if not Cnf.FindB("Dinstall::SecurityQueueBuild") and not os.path.islink(filename):
+            utils.fubar("%s (from queue_build) should be a symlink but isn't." % (filename))
+        os.unlink(filename)
+        count += 1
+    projectB.query("DELETE FROM queue_build WHERE last_used <= '%s'" % (our_delete_date))
+
+    if count:
+        sys.stderr.write("Cleaned %d queue_build files.\n" % (count))
+
+################################################################################
+
+def main():
+    global Cnf, Options, projectB, delete_date, now_date
+
+    Cnf = utils.get_conf()
+    for i in ["Help", "No-Action" ]:
+        if not Cnf.has_key("Clean-Suites::Options::%s" % (i)):
+            Cnf["Clean-Suites::Options::%s" % (i)] = ""
+
+    Arguments = [('h',"help","Clean-Suites::Options::Help"),
+                 ('n',"no-action","Clean-Suites::Options::No-Action")]
+
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Clean-Suites::Options")
+
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+
+    now_date = time.strftime("%Y-%m-%d %H:%M")
+    delete_date = time.strftime("%Y-%m-%d %H:%M", time.localtime(time.time()-int(Cnf["Clean-Suites::StayOfExecution"])))
+
+    check_binaries()
+    clean_binaries()
+    check_sources()
+    check_files()
+    clean()
+    clean_maintainers()
+    clean_fingerprints()
+    clean_queue_build()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/compare_suites.py b/dak/compare_suites.py
new file mode 100755 (executable)
index 0000000..8c36758
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+
+# Check for fixable discrepancies between stable and unstable
+# Copyright (C) 2000, 2001, 2002, 2003, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+################################################################################
+
+import pg, sys
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak compare-suites
+Looks for fixable descrepancies between stable and unstable.
+
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def main ():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Compare-Suites::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Compare-Suites::Options::%s" % (i)):
+            Cnf["Compare-Suites::Options::%s" % (i)] = ""
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Compare-Suites::Options")
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    src_suite = "stable"
+    dst_suite = "unstable"
+
+    src_suite_id = database.get_suite_id(src_suite)
+    dst_suite_id = database.get_suite_id(dst_suite)
+    arch_all_id = database.get_architecture_id("all")
+    dsc_type_id = database.get_override_type_id("dsc")
+
+    for arch in Cnf.ValueList("Suite::%s::Architectures" % (src_suite)):
+        if arch == "source":
+            continue
+
+        # Arch: all doesn't work; consider packages which go from
+        # arch: all to arch: any, e.g. debconf... needs more checks
+        # and thought later.
+
+        if arch == "all":
+            continue
+        arch_id = database.get_architecture_id(arch)
+        q = projectB.query("""
+SELECT b_src.package, b_src.version, a.arch_string
+  FROM binaries b_src, bin_associations ba, override o, architecture a
+  WHERE ba.bin = b_src.id AND ba.suite = %s AND b_src.architecture = %s
+        AND a.id = b_src.architecture AND o.package = b_src.package
+        AND o.suite = %s AND o.type != %s AND NOT EXISTS
+    (SELECT 1 FROM bin_associations ba2, binaries b_dst
+       WHERE ba2.bin = b_dst.id AND b_dst.package = b_src.package
+             AND (b_dst.architecture = %s OR b_dst.architecture = %s)
+             AND ba2.suite = %s AND EXISTS
+               (SELECT 1 FROM bin_associations ba3, binaries b2
+                  WHERE ba3.bin = b2.id AND ba3.suite = %s AND b2.package = b_dst.package))
+ORDER BY b_src.package;"""
+                           % (src_suite_id, arch_id, dst_suite_id, dsc_type_id, arch_id, arch_all_id, dst_suite_id, dst_suite_id))
+        for i in q.getresult():
+            print " ".join(i)
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/control_overrides.py b/dak/control_overrides.py
new file mode 100644 (file)
index 0000000..0af5c48
--- /dev/null
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+
+# Bulk manipulation of the overrides
+# Copyright (C) 2000, 2001, 2002, 2003, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# On 30 Nov 1998, James Troup wrote:
+#
+# > James Troup<2> <troup2@debian.org>
+# >
+# >    James is a clone of James; he's going to take over the world.
+# >    After he gets some sleep.
+#
+# Could you clone other things too? Sheep? Llamas? Giant mutant turnips?
+#
+# Your clone will need some help to take over the world, maybe clone up an
+# army of penguins and threaten to unleash them on the world, forcing
+# governments to sway to the new James' will!
+#
+# Yes, I can envision a day when James' duplicate decides to take a horrific
+# vengance on the James that spawned him and unleashes his fury in the form
+# of thousands upon thousands of chickens that look just like Captin Blue
+# Eye! Oh the horror.
+#
+# Now you'll have to were name tags to people can tell you apart, unless of
+# course the new clone is truely evil in which case he should be easy to
+# identify!
+#
+# Jason
+# Chicken. Black. Helicopters.
+# Be afraid.
+
+# <Pine.LNX.3.96.981130011300.30365Z-100000@wakko>
+
+################################################################################
+
+import pg, sys, time
+import apt_pkg
+from daklib import utils
+from daklib import database
+from daklib import logging
+
+################################################################################
+
+Cnf = None
+projectB = None
+Logger = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak control-overrides [OPTIONS]
+  -h, --help               print this help and exit
+
+  -c, --component=CMPT     list/set overrides by component
+                                  (contrib,*main,non-free)
+  -s, --suite=SUITE        list/set overrides by suite
+                                  (experimental,stable,testing,*unstable)
+  -t, --type=TYPE          list/set overrides by type
+                                  (*deb,dsc,udeb)
+
+  -a, --add                add overrides (changes and deletions are ignored)
+  -S, --set                set overrides
+  -l, --list               list overrides
+
+  -q, --quiet              be less verbose
+
+ starred (*) values are default"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def process_file (file, suite, component, type, action):
+    suite_id = database.get_suite_id(suite)
+    if suite_id == -1:
+        utils.fubar("Suite '%s' not recognised." % (suite))
+
+    component_id = database.get_component_id(component)
+    if component_id == -1:
+        utils.fubar("Component '%s' not recognised." % (component))
+
+    type_id = database.get_override_type_id(type)
+    if type_id == -1:
+        utils.fubar("Type '%s' not recognised. (Valid types are deb, udeb and dsc.)" % (type))
+
+    # --set is done mostly internal for performance reasons; most
+    # invocations of --set will be updates and making people wait 2-3
+    # minutes while 6000 select+inserts are run needlessly isn't cool.
+
+    original = {}
+    new = {}
+    c_skipped = 0
+    c_added = 0
+    c_updated = 0
+    c_removed = 0
+    c_error = 0
+
+    q = projectB.query("SELECT o.package, o.priority, o.section, o.maintainer, p.priority, s.section FROM override o, priority p, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s and o.priority = p.id and o.section = s.id"
+                       % (suite_id, component_id, type_id))
+    for i in q.getresult():
+        original[i[0]] = i[1:]
+
+    start_time = time.time()
+    projectB.query("BEGIN WORK")
+    for line in file.readlines():
+        line = utils.re_comments.sub('', line).strip()
+        if line == "":
+            continue
+
+        maintainer_override = None
+        if type == "dsc":
+            split_line = line.split(None, 2)
+            if len(split_line) == 2:
+                (package, section) = split_line
+            elif len(split_line) == 3:
+                (package, section, maintainer_override) = split_line
+            else:
+                utils.warn("'%s' does not break into 'package section [maintainer-override]'." % (line))
+                c_error += 1
+                continue
+            priority = "source"
+        else: # binary or udeb
+            split_line = line.split(None, 3)
+            if len(split_line) == 3:
+                (package, priority, section) = split_line
+            elif len(split_line) == 4:
+                (package, priority, section, maintainer_override) = split_line
+            else:
+                utils.warn("'%s' does not break into 'package priority section [maintainer-override]'." % (line))
+                c_error += 1
+                continue
+
+        section_id = database.get_section_id(section)
+        if section_id == -1:
+            utils.warn("'%s' is not a valid section. ['%s' in suite %s, component %s]." % (section, package, suite, component))
+            c_error += 1
+            continue
+        priority_id = database.get_priority_id(priority)
+        if priority_id == -1:
+            utils.warn("'%s' is not a valid priority. ['%s' in suite %s, component %s]." % (priority, package, suite, component))
+            c_error += 1
+            continue
+
+        if new.has_key(package):
+            utils.warn("Can't insert duplicate entry for '%s'; ignoring all but the first. [suite %s, component %s]" % (package, suite, component))
+            c_error += 1
+            continue
+        new[package] = ""
+        if original.has_key(package):
+            (old_priority_id, old_section_id, old_maintainer_override, old_priority, old_section) = original[package]
+            if action == "add" or old_priority_id == priority_id and \
+               old_section_id == section_id and \
+               ((old_maintainer_override == maintainer_override) or \
+                (old_maintainer_override == "" and maintainer_override == None)):
+                # If it's unchanged or we're in 'add only' mode, ignore it
+                c_skipped += 1
+                continue
+            else:
+                # If it's changed, delete the old one so we can
+                # reinsert it with the new information
+                c_updated += 1
+                projectB.query("DELETE FROM override WHERE suite = %s AND component = %s AND package = '%s' AND type = %s"
+                               % (suite_id, component_id, package, type_id))
+                # Log changes
+                if old_priority_id != priority_id:
+                    Logger.log(["changed priority",package,old_priority,priority])
+                if old_section_id != section_id:
+                    Logger.log(["changed section",package,old_section,section])
+                if old_maintainer_override != maintainer_override:
+                    Logger.log(["changed maintainer override",package,old_maintainer_override,maintainer_override])
+                update_p = 1
+        else:
+            c_added += 1
+            update_p = 0
+
+        if maintainer_override:
+            projectB.query("INSERT INTO override (suite, component, type, package, priority, section, maintainer) VALUES (%s, %s, %s, '%s', %s, %s, '%s')"
+                           % (suite_id, component_id, type_id, package, priority_id, section_id, maintainer_override))
+        else:
+            projectB.query("INSERT INTO override (suite, component, type, package, priority, section,maintainer) VALUES (%s, %s, %s, '%s', %s, %s, '')"
+                           % (suite_id, component_id, type_id, package, priority_id, section_id))
+
+        if not update_p:
+            Logger.log(["new override",suite,component,type,package,priority,section,maintainer_override])
+
+    if not action == "add":
+        # Delete any packages which were removed
+        for package in original.keys():
+            if not new.has_key(package):
+                projectB.query("DELETE FROM override WHERE suite = %s AND component = %s AND package = '%s' AND type = %s"
+                               % (suite_id, component_id, package, type_id))
+                c_removed += 1
+                Logger.log(["removed override",suite,component,type,package])
+
+    projectB.query("COMMIT WORK")
+    if not Cnf["Control-Overrides::Options::Quiet"]:
+        print "Done in %d seconds. [Updated = %d, Added = %d, Removed = %d, Skipped = %d, Errors = %d]" % (int(time.time()-start_time), c_updated, c_added, c_removed, c_skipped, c_error)
+    Logger.log(["set complete",c_updated, c_added, c_removed, c_skipped, c_error])
+
+################################################################################
+
+def list_overrides(suite, component, type):
+    suite_id = database.get_suite_id(suite)
+    if suite_id == -1:
+        utils.fubar("Suite '%s' not recognised." % (suite))
+
+    component_id = database.get_component_id(component)
+    if component_id == -1:
+        utils.fubar("Component '%s' not recognised." % (component))
+
+    type_id = database.get_override_type_id(type)
+    if type_id == -1:
+        utils.fubar("Type '%s' not recognised. (Valid types are deb, udeb and dsc)" % (type))
+
+    if type == "dsc":
+        q = projectB.query("SELECT o.package, s.section, o.maintainer FROM override o, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.section = s.id ORDER BY s.section, o.package" % (suite_id, component_id, type_id))
+        for i in q.getresult():
+            print utils.result_join(i)
+    else:
+        q = projectB.query("SELECT o.package, p.priority, s.section, o.maintainer, p.level FROM override o, priority p, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.priority = p.id AND o.section = s.id ORDER BY s.section, p.level, o.package" % (suite_id, component_id, type_id))
+        for i in q.getresult():
+            print utils.result_join(i[:-1])
+
+################################################################################
+
+def main ():
+    global Cnf, projectB, Logger
+
+    Cnf = utils.get_conf()
+    Arguments = [('a', "add", "Control-Overrides::Options::Add"),
+                 ('c', "component", "Control-Overrides::Options::Component", "HasArg"),
+                 ('h', "help", "Control-Overrides::Options::Help"),
+                 ('l', "list", "Control-Overrides::Options::List"),
+                 ('q', "quiet", "Control-Overrides::Options::Quiet"),
+                 ('s', "suite", "Control-Overrides::Options::Suite", "HasArg"),
+                 ('S', "set", "Control-Overrides::Options::Set"),
+                 ('t', "type", "Control-Overrides::Options::Type", "HasArg")]
+
+    # Default arguments
+    for i in [ "add", "help", "list", "quiet", "set" ]:
+        if not Cnf.has_key("Control-Overrides::Options::%s" % (i)):
+            Cnf["Control-Overrides::Options::%s" % (i)] = ""
+    if not Cnf.has_key("Control-Overrides::Options::Component"):
+        Cnf["Control-Overrides::Options::Component"] = "main"
+    if not Cnf.has_key("Control-Overrides::Options::Suite"):
+        Cnf["Control-Overrides::Options::Suite"] = "unstable"
+    if not Cnf.has_key("Control-Overrides::Options::Type"):
+        Cnf["Control-Overrides::Options::Type"] = "deb"
+
+    file_list = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+
+    if Cnf["Control-Overrides::Options::Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    action = None
+    for i in [ "add", "list", "set" ]:
+        if Cnf["Control-Overrides::Options::%s" % (i)]:
+            if action:
+                utils.fubar("Can not perform more than one action at once.")
+            action = i
+
+    (suite, component, otype) = (Cnf["Control-Overrides::Options::Suite"],
+                                 Cnf["Control-Overrides::Options::Component"],
+                                 Cnf["Control-Overrides::Options::Type"])
+
+    if action == "list":
+        list_overrides(suite, component, otype)
+    else:
+        if Cnf.has_key("Suite::%s::Untouchable" % suite) and Cnf["Suite::%s::Untouchable" % suite] != 0:
+            utils.fubar("%s: suite is untouchable" % suite)
+
+        Logger = logging.Logger(Cnf, "control-overrides")
+        if file_list:
+            for f in file_list:
+                process_file(utils.open_file(f), suite, component, otype, action)
+        else:
+            process_file(sys.stdin, suite, component, otype, action)
+        Logger.close()
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/control_suite.py b/dak/control_suite.py
new file mode 100644 (file)
index 0000000..4b704b9
--- /dev/null
@@ -0,0 +1,295 @@
+#!/usr/bin/env python
+
+# Manipulate suite tags
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#######################################################################################
+
+# 8to6Guy: "Wow, Bob, You look rough!"
+# BTAF: "Mbblpmn..."
+# BTAF <.oO>: "You moron! This is what you get for staying up all night drinking vodka and salad dressing!"
+# BTAF <.oO>: "This coffee I.V. drip is barely even keeping me awake! I need something with more kick! But what?"
+# BTAF: "OMIGOD! I OVERDOSED ON HEROIN"
+# CoWorker#n: "Give him air!!"
+# CoWorker#n+1: "We need a syringe full of adrenaline!"
+# CoWorker#n+2: "Stab him in the heart!"
+# BTAF: "*YES!*"
+# CoWorker#n+3: "Bob's been overdosing quite a bit lately..."
+# CoWorker#n+4: "Third time this week."
+
+# -- http://www.angryflower.com/8to6.gif
+
+#######################################################################################
+
+# Adds or removes packages from a suite.  Takes the list of files
+# either from stdin or as a command line argument.  Special action
+# "set", will reset the suite (!) and add all packages from scratch.
+
+#######################################################################################
+
+import pg, sys
+import apt_pkg
+from daklib import database
+from daklib import logging
+from daklib import utils
+
+#######################################################################################
+
+Cnf = None
+projectB = None
+Logger = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak control-suite [OPTIONS] [FILE]
+Display or alter the contents of a suite using FILE(s), or stdin.
+
+  -a, --add=SUITE            add to SUITE
+  -h, --help                 show this help and exit
+  -l, --list=SUITE           list the contents of SUITE
+  -r, --remove=SUITE         remove from SUITE
+  -s, --set=SUITE            set SUITE"""
+
+    sys.exit(exit_code)
+
+#######################################################################################
+
+def get_id (package, version, architecture):
+    if architecture == "source":
+        q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
+    else:
+        q = projectB.query("SELECT b.id FROM binaries b, architecture a WHERE b.package = '%s' AND b.version = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all') AND b.architecture = a.id" % (package, version, architecture))
+
+    ql = q.getresult()
+    if not ql:
+        utils.warn("Couldn't find '%s_%s_%s'." % (package, version, architecture))
+        return None
+    if len(ql) > 1:
+        utils.warn("Found more than one match for '%s_%s_%s'." % (package, version, architecture))
+        return None
+    return ql[0][0]
+
+#######################################################################################
+
+def set_suite (file, suite_id):
+    lines = file.readlines()
+
+    projectB.query("BEGIN WORK")
+
+    # Build up a dictionary of what is currently in the suite
+    current = {}
+    q = projectB.query("SELECT b.package, b.version, a.arch_string, ba.id FROM binaries b, bin_associations ba, architecture a WHERE ba.suite = %s AND ba.bin = b.id AND b.architecture = a.id" % (suite_id))
+    ql = q.getresult()
+    for i in ql:
+        key = " ".join(i[:3])
+        current[key] = i[3]
+    q = projectB.query("SELECT s.source, s.version, sa.id FROM source s, src_associations sa WHERE sa.suite = %s AND sa.source = s.id" % (suite_id))
+    ql = q.getresult()
+    for i in ql:
+        key = " ".join(i[:2]) + " source"
+        current[key] = i[2]
+
+    # Build up a dictionary of what should be in the suite
+    desired = {}
+    for line in lines:
+        split_line = line.strip().split()
+        if len(split_line) != 3:
+            utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1]))
+            continue
+        key = " ".join(split_line)
+        desired[key] = ""
+
+    # Check to see which packages need removed and remove them
+    for key in current.keys():
+        if not desired.has_key(key):
+            (package, version, architecture) = key.split()
+            pkid = current[key]
+            if architecture == "source":
+                q = projectB.query("DELETE FROM src_associations WHERE id = %s" % (pkid))
+            else:
+                q = projectB.query("DELETE FROM bin_associations WHERE id = %s" % (pkid))
+            Logger.log(["removed", key, pkid])
+
+    # Check to see which packages need added and add them
+    for key in desired.keys():
+        if not current.has_key(key):
+            (package, version, architecture) = key.split()
+            pkid = get_id (package, version, architecture)
+            if not pkid:
+                continue
+            if architecture == "source":
+                q = projectB.query("INSERT INTO src_associations (suite, source) VALUES (%s, %s)" % (suite_id, pkid))
+            else:
+                q = projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%s, %s)" % (suite_id, pkid))
+            Logger.log(["added", key, pkid])
+
+    projectB.query("COMMIT WORK")
+
+#######################################################################################
+
+def process_file (file, suite, action):
+
+    suite_id = database.get_suite_id(suite)
+
+    if action == "set":
+        set_suite (file, suite_id)
+        return
+
+    lines = file.readlines()
+
+    projectB.query("BEGIN WORK")
+
+    for line in lines:
+        split_line = line.strip().split()
+        if len(split_line) != 3:
+            utils.warn("'%s' does not break into 'package version architecture'." % (line[:-1]))
+            continue
+
+        (package, version, architecture) = split_line
+
+        pkid = get_id(package, version, architecture)
+        if not pkid:
+            continue
+
+        if architecture == "source":
+            # Find the existing assoications ID, if any
+            q = projectB.query("SELECT id FROM src_associations WHERE suite = %s and source = %s" % (suite_id, pkid))
+            ql = q.getresult()
+            if not ql:
+                assoication_id = None
+            else:
+                assoication_id = ql[0][0]
+            # Take action
+            if action == "add":
+                if assoication_id:
+                    utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite))
+                    continue
+                else:
+                    q = projectB.query("INSERT INTO src_associations (suite, source) VALUES (%s, %s)" % (suite_id, pkid))
+            elif action == "remove":
+                if assoication_id == None:
+                    utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite))
+                    continue
+                else:
+                    q = projectB.query("DELETE FROM src_associations WHERE id = %s" % (assoication_id))
+        else:
+            # Find the existing assoications ID, if any
+            q = projectB.query("SELECT id FROM bin_associations WHERE suite = %s and bin = %s" % (suite_id, pkid))
+            ql = q.getresult()
+            if not ql:
+                assoication_id = None
+            else:
+                assoication_id = ql[0][0]
+            # Take action
+            if action == "add":
+                if assoication_id:
+                    utils.warn("'%s_%s_%s' already exists in suite %s." % (package, version, architecture, suite))
+                    continue
+                else:
+                    q = projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%s, %s)" % (suite_id, pkid))
+            elif action == "remove":
+                if assoication_id == None:
+                    utils.warn("'%s_%s_%s' doesn't exist in suite %s." % (package, version, architecture, suite))
+                    continue
+                else:
+                    q = projectB.query("DELETE FROM bin_associations WHERE id = %s" % (assoication_id))
+
+    projectB.query("COMMIT WORK")
+
+#######################################################################################
+
+def get_list (suite):
+    suite_id = database.get_suite_id(suite)
+    # List binaries
+    q = projectB.query("SELECT b.package, b.version, a.arch_string FROM binaries b, bin_associations ba, architecture a WHERE ba.suite = %s AND ba.bin = b.id AND b.architecture = a.id" % (suite_id))
+    ql = q.getresult()
+    for i in ql:
+        print " ".join(i)
+
+    # List source
+    q = projectB.query("SELECT s.source, s.version FROM source s, src_associations sa WHERE sa.suite = %s AND sa.source = s.id" % (suite_id))
+    ql = q.getresult()
+    for i in ql:
+        print " ".join(i) + " source"
+
+#######################################################################################
+
+def main ():
+    global Cnf, projectB, Logger
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('a',"add","Control-Suite::Options::Add", "HasArg"),
+                 ('h',"help","Control-Suite::Options::Help"),
+                 ('l',"list","Control-Suite::Options::List","HasArg"),
+                 ('r',"remove", "Control-Suite::Options::Remove", "HasArg"),
+                 ('s',"set", "Control-Suite::Options::Set", "HasArg")]
+
+    for i in ["add", "help", "list", "remove", "set", "version" ]:
+        if not Cnf.has_key("Control-Suite::Options::%s" % (i)):
+            Cnf["Control-Suite::Options::%s" % (i)] = ""
+
+    try:
+        file_list = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
+    except SystemError, e:
+        print "%s\n" % e
+        usage(1)
+    Options = Cnf.SubTree("Control-Suite::Options")
+
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"],int(Cnf["DB::Port"]))
+
+    database.init(Cnf, projectB)
+
+    action = None
+
+    for i in ("add", "list", "remove", "set"):
+        if Cnf["Control-Suite::Options::%s" % (i)] != "":
+            suite = Cnf["Control-Suite::Options::%s" % (i)]
+            if database.get_suite_id(suite) == -1:
+                utils.fubar("Unknown suite '%s'." %(suite))
+            else:
+                if action:
+                    utils.fubar("Can only perform one action at a time.")
+                action = i
+
+    # Need an action...
+    if action == None:
+        utils.fubar("No action specified.")
+
+    # Safety/Sanity check
+    if action == "set" and suite not in ["testing", "etch-m68k"]:
+        utils.fubar("Will not reset a suite other than testing.")
+
+    if action == "list":
+        get_list(suite)
+    else:
+        Logger = logging.Logger(Cnf, "control-suite")
+        if file_list:
+            for f in file_list:
+                process_file(utils.open_file(f), suite, action)
+        else:
+            process_file(sys.stdin, suite, action)
+        Logger.close()
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/cruft_report.py b/dak/cruft_report.py
new file mode 100755 (executable)
index 0000000..fab47bf
--- /dev/null
@@ -0,0 +1,551 @@
+#!/usr/bin/env python
+
+# Check for obsolete binary packages
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# ``If you're claiming that's a "problem" that needs to be "fixed",
+#   you might as well write some letters to God about how unfair entropy
+#   is while you're at it.'' -- 20020802143104.GA5628@azure.humbug.org.au
+
+## TODO:  fix NBS looping for version, implement Dubious NBS, fix up output of duplicate source package stuff, improve experimental ?, add overrides, avoid ANAIS for duplicated packages
+
+################################################################################
+
+import commands, pg, os, sys, time, re
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+suite = "unstable" # Default
+suite_id = None
+no_longer_in_suite = {}; # Really should be static to add_nbs, but I'm lazy
+
+source_binaries = {}
+source_versions = {}
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak cruft-report
+Check for obsolete or duplicated packages.
+
+  -h, --help                show this help and exit.
+  -m, --mode=MODE           chose the MODE to run in (full or daily).
+  -s, --suite=SUITE         check suite SUITE.
+  -w, --wanna-build-dump    where to find the copies of http://buildd.debian.org/stats/*.txt"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def add_nbs(nbs_d, source, version, package):
+    # Ensure the package is still in the suite (someone may have already removed it)
+    if no_longer_in_suite.has_key(package):
+        return
+    else:
+        q = projectB.query("SELECT b.id FROM binaries b, bin_associations ba WHERE ba.bin = b.id AND ba.suite = %s AND b.package = '%s' LIMIT 1" % (suite_id, package))
+        if not q.getresult():
+            no_longer_in_suite[package] = ""
+            return
+
+    nbs_d.setdefault(source, {})
+    nbs_d[source].setdefault(version, {})
+    nbs_d[source][version][package] = ""
+
+################################################################################
+
+# Check for packages built on architectures they shouldn't be.
+def do_anais(architecture, binaries_list, source):
+    if architecture == "any" or architecture == "all":
+        return ""
+
+    anais_output = ""
+    architectures = {}
+    for arch in architecture.split():
+        architectures[arch.strip()] = ""
+    for binary in binaries_list:
+        q = projectB.query("SELECT a.arch_string, b.version FROM binaries b, bin_associations ba, architecture a WHERE ba.suite = %s AND ba.bin = b.id AND b.architecture = a.id AND b.package = '%s'" % (suite_id, binary))
+        ql = q.getresult()
+        versions = []
+        for i in ql:
+            arch = i[0]
+            version = i[1]
+            if architectures.has_key(arch):
+                versions.append(version)
+        versions.sort(apt_pkg.VersionCompare)
+        if versions:
+            latest_version = versions.pop()
+        else:
+            latest_version = None
+        # Check for 'invalid' architectures
+        versions_d = {}
+        for i in ql:
+            arch = i[0]
+            version = i[1]
+            if not architectures.has_key(arch):
+                versions_d.setdefault(version, [])
+                versions_d[version].append(arch)
+
+        if versions_d != {}:
+            anais_output += "\n (*) %s_%s [%s]: %s\n" % (binary, latest_version, source, architecture)
+            versions = versions_d.keys()
+            versions.sort(apt_pkg.VersionCompare)
+            for version in versions:
+                arches = versions_d[version]
+                arches.sort()
+                anais_output += "    o %s: %s\n" % (version, ", ".join(arches))
+    return anais_output
+
+
+################################################################################
+
+# Check for out-of-date binaries on architectures that do not want to build that
+# package any more, and have them listed as Not-For-Us
+def do_nfu(nfu_packages):
+    output = ""
+    
+    a2p = {}
+
+    for architecture in nfu_packages:
+        a2p[architecture] = []
+        for (package,bver,sver) in nfu_packages[architecture]:
+            output += "  * [%s] does not want %s (binary %s, source %s)\n" % (architecture, package, bver, sver)
+            a2p[architecture].append(package)
+
+
+    if output:
+        print "Obsolete by Not-For-Us"
+        print "----------------------"
+        print
+        print output
+
+        print "Suggested commands:"
+        for architecture in a2p:
+            if a2p[architecture]:
+                print (" dak rm -m \"[auto-cruft] NFU\" -s %s -a %s -b %s" % 
+                    (suite, architecture, " ".join(a2p[architecture])))
+        print
+
+def parse_nfu(architecture):
+    # utils/hpodder_1.1.5.0: Not-For-Us [optional:out-of-date]
+    r = re.compile("^\w+/([^_]+)_.*: Not-For-Us")
+
+    ret = set()
+    
+    filename = "%s/%s-all.txt" % (Cnf["Cruft-Report::Options::Wanna-Build-Dump"], architecture)
+
+    # Not all architectures may have a wanna-build dump, so we want to ignore missin
+    # files
+    if os.path.exists(filename):
+        f = utils.open_file(filename)
+        for line in f:
+            if line[0] == ' ':
+                continue
+
+            m = r.match(line)
+            if m:
+                ret.add(m.group(1))
+
+        f.close()
+    else:
+        utils.warn("No wanna-build dump file for architecture %s", architecture)
+    return ret
+
+################################################################################
+
+def do_nviu():
+    experimental_id = database.get_suite_id("experimental")
+    if experimental_id == -1:
+        return
+    # Check for packages in experimental obsoleted by versions in unstable
+    q = projectB.query("""
+SELECT s.source, s.version AS experimental, s2.version AS unstable
+  FROM src_associations sa, source s, source s2, src_associations sa2
+  WHERE sa.suite = %s AND sa2.suite = %d AND sa.source = s.id
+   AND sa2.source = s2.id AND s.source = s2.source
+   AND versioncmp(s.version, s2.version) < 0""" % (experimental_id,
+                                                   database.get_suite_id("unstable")))
+    ql = q.getresult()
+    if ql:
+        nviu_to_remove = []
+        print "Newer version in unstable"
+        print "-------------------------"
+        print
+        for i in ql:
+            (source, experimental_version, unstable_version) = i
+            print " o %s (%s, %s)" % (source, experimental_version, unstable_version)
+            nviu_to_remove.append(source)
+        print
+        print "Suggested command:"
+        print " dak rm -m \"[auto-cruft] NVIU\" -s experimental %s" % (" ".join(nviu_to_remove))
+        print
+
+################################################################################
+
+def do_nbs(real_nbs):
+    output = "Not Built from Source\n"
+    output += "---------------------\n\n"
+
+    nbs_to_remove = []
+    nbs_keys = real_nbs.keys()
+    nbs_keys.sort()
+    for source in nbs_keys:
+        output += " * %s_%s builds: %s\n" % (source,
+                                       source_versions.get(source, "??"),
+                                       source_binaries.get(source, "(source does not exist)"))
+        output += "      but no longer builds:\n"
+        versions = real_nbs[source].keys()
+        versions.sort(apt_pkg.VersionCompare)
+        for version in versions:
+            packages = real_nbs[source][version].keys()
+            packages.sort()
+            for pkg in packages:
+                nbs_to_remove.append(pkg)
+            output += "        o %s: %s\n" % (version, ", ".join(packages))
+
+        output += "\n"
+
+    if nbs_to_remove:
+        print output
+
+        print "Suggested command:"
+        print " dak rm -m \"[auto-cruft] NBS\" -s %s -b %s" % (suite, " ".join(nbs_to_remove))
+        print
+
+################################################################################
+
+def do_dubious_nbs(dubious_nbs):
+    print "Dubious NBS"
+    print "-----------"
+    print
+
+    dubious_nbs_keys = dubious_nbs.keys()
+    dubious_nbs_keys.sort()
+    for source in dubious_nbs_keys:
+        print " * %s_%s builds: %s" % (source,
+                                       source_versions.get(source, "??"),
+                                       source_binaries.get(source, "(source does not exist)"))
+        print "      won't admit to building:"
+        versions = dubious_nbs[source].keys()
+        versions.sort(apt_pkg.VersionCompare)
+        for version in versions:
+            packages = dubious_nbs[source][version].keys()
+            packages.sort()
+            print "        o %s: %s" % (version, ", ".join(packages))
+
+        print
+
+################################################################################
+
+def do_obsolete_source(duplicate_bins, bin2source):
+    obsolete = {}
+    for key in duplicate_bins.keys():
+        (source_a, source_b) = key.split('_')
+        for source in [ source_a, source_b ]:
+            if not obsolete.has_key(source):
+                if not source_binaries.has_key(source):
+                    # Source has already been removed
+                    continue
+                else:
+                    obsolete[source] = [ i.strip() for i in source_binaries[source].split(',') ]
+            for binary in duplicate_bins[key]:
+                if bin2source.has_key(binary) and bin2source[binary]["source"] == source:
+                    continue
+                if binary in obsolete[source]:
+                    obsolete[source].remove(binary)
+
+    to_remove = []
+    output = "Obsolete source package\n"
+    output += "-----------------------\n\n"
+    obsolete_keys = obsolete.keys()
+    obsolete_keys.sort()
+    for source in obsolete_keys:
+        if not obsolete[source]:
+            to_remove.append(source)
+            output += " * %s (%s)\n" % (source, source_versions[source])
+            for binary in [ i.strip() for i in source_binaries[source].split(',') ]:
+                if bin2source.has_key(binary):
+                    output += "    o %s (%s) is built by %s.\n" \
+                          % (binary, bin2source[binary]["version"],
+                             bin2source[binary]["source"])
+                else:
+                    output += "    o %s is not built.\n" % binary
+            output += "\n"
+
+    if to_remove:
+        print output
+
+        print "Suggested command:"
+        print " dak rm -S -p -m \"[auto-cruft] obsolete source package\" %s" % (" ".join(to_remove))
+        print
+
+def get_suite_binaries():
+    # Initalize a large hash table of all binary packages
+    binaries = {}
+    before = time.time()
+
+    sys.stderr.write("[Getting a list of binary packages in %s..." % (suite))
+    q = projectB.query("SELECT distinct b.package FROM binaries b, bin_associations ba WHERE ba.suite = %s AND ba.bin = b.id" % (suite_id))
+    ql = q.getresult()
+    sys.stderr.write("done. (%d seconds)]\n" % (int(time.time()-before)))
+    for i in ql:
+        binaries[i[0]] = ""
+
+    return binaries
+
+################################################################################
+
+def main ():
+    global Cnf, projectB, suite, suite_id, source_binaries, source_versions
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Cruft-Report::Options::Help"),
+                 ('m',"mode","Cruft-Report::Options::Mode", "HasArg"),
+                 ('s',"suite","Cruft-Report::Options::Suite","HasArg"),
+                 ('w',"wanna-build-dump","Cruft-Report::Options::Wanna-Build-Dump","HasArg")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Cruft-Report::Options::%s" % (i)):
+            Cnf["Cruft-Report::Options::%s" % (i)] = ""
+    Cnf["Cruft-Report::Options::Suite"] = Cnf["Dinstall::DefaultSuite"]
+
+    if not Cnf.has_key("Cruft-Report::Options::Mode"):
+        Cnf["Cruft-Report::Options::Mode"] = "daily"
+
+    if not Cnf.has_key("Cruft-Report::Options::Wanna-Build-Dump"):
+        Cnf["Cruft-Report::Options::Wanna-Build-Dump"] = "/srv/ftp.debian.org/scripts/nfu"
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Cruft-Report::Options")
+    if Options["Help"]:
+        usage()
+
+    # Set up checks based on mode
+    if Options["Mode"] == "daily":
+        checks = [ "nbs", "nviu", "obsolete source" ]
+    elif Options["Mode"] == "full":
+        checks = [ "nbs", "nviu", "obsolete source", "nfu", "dubious nbs", "bnb", "bms", "anais" ]
+    else:
+        utils.warn("%s is not a recognised mode - only 'full' or 'daily' are understood." % (Options["Mode"]))
+        usage(1)
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    bin_pkgs = {}
+    src_pkgs = {}
+    bin2source = {}
+    bins_in_suite = {}
+    nbs = {}
+    source_versions = {}
+
+    anais_output = ""
+    duplicate_bins = {}
+
+    nfu_packages = {}
+
+    suite = Options["Suite"]
+    suite_id = database.get_suite_id(suite)
+
+    bin_not_built = {}
+
+    if "bnb" in checks:
+        bins_in_suite = get_suite_binaries()
+
+    # Checks based on the Sources files
+    components = Cnf.ValueList("Suite::%s::Components" % (suite))
+    for component in components:
+        filename = "%s/dists/%s/%s/source/Sources.gz" % (Cnf["Dir::Root"], suite, component)
+        # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
+        temp_filename = utils.temp_filename()
+        (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
+        if (result != 0):
+            sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
+            sys.exit(result)
+        sources = utils.open_file(temp_filename)
+        Sources = apt_pkg.ParseTagFile(sources)
+        while Sources.Step():
+            source = Sources.Section.Find('Package')
+            source_version = Sources.Section.Find('Version')
+            architecture = Sources.Section.Find('Architecture')
+            binaries = Sources.Section.Find('Binary')
+            binaries_list = [ i.strip() for i in  binaries.split(',') ]
+
+            if "bnb" in checks:
+                # Check for binaries not built on any architecture.
+                for binary in binaries_list:
+                    if not bins_in_suite.has_key(binary):
+                        bin_not_built.setdefault(source, {})
+                        bin_not_built[source][binary] = ""
+
+            if "anais" in checks:
+                anais_output += do_anais(architecture, binaries_list, source)
+
+            # Check for duplicated packages and build indices for checking "no source" later
+            source_index = component + '/' + source
+            if src_pkgs.has_key(source):
+                print " %s is a duplicated source package (%s and %s)" % (source, source_index, src_pkgs[source])
+            src_pkgs[source] = source_index
+            for binary in binaries_list:
+                if bin_pkgs.has_key(binary):
+                    key_list = [ source, bin_pkgs[binary] ]
+                    key_list.sort()
+                    key = '_'.join(key_list)
+                    duplicate_bins.setdefault(key, [])
+                    duplicate_bins[key].append(binary)
+                bin_pkgs[binary] = source
+            source_binaries[source] = binaries
+            source_versions[source] = source_version
+
+        sources.close()
+        os.unlink(temp_filename)
+
+    # Checks based on the Packages files
+    check_components = components[:]
+    if suite != "experimental":
+        check_components.append('main/debian-installer');
+    for component in check_components:
+        architectures = filter(utils.real_arch, Cnf.ValueList("Suite::%s::Architectures" % (suite)))
+        for architecture in architectures:
+            filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (Cnf["Dir::Root"], suite, component, architecture)
+            # apt_pkg.ParseTagFile needs a real file handle
+            temp_filename = utils.temp_filename()
+            (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
+            if (result != 0):
+                sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
+                sys.exit(result)
+
+            if "nfu" in checks:
+                nfu_packages.setdefault(architecture,[])
+                nfu_entries = parse_nfu(architecture)
+
+            packages = utils.open_file(temp_filename)
+            Packages = apt_pkg.ParseTagFile(packages)
+            while Packages.Step():
+                package = Packages.Section.Find('Package')
+                source = Packages.Section.Find('Source', "")
+                version = Packages.Section.Find('Version')
+                if source == "":
+                    source = package
+                if bin2source.has_key(package) and \
+                       apt_pkg.VersionCompare(version, bin2source[package]["version"]) > 0:
+                    bin2source[package]["version"] = version
+                    bin2source[package]["source"] = source
+                else:
+                    bin2source[package] = {}
+                    bin2source[package]["version"] = version
+                    bin2source[package]["source"] = source
+                if source.find("(") != -1:
+                    m = utils.re_extract_src_version.match(source)
+                    source = m.group(1)
+                    version = m.group(2)
+                if not bin_pkgs.has_key(package):
+                    nbs.setdefault(source,{})
+                    nbs[source].setdefault(package, {})
+                    nbs[source][package][version] = ""
+                else:
+                    previous_source = bin_pkgs[package]
+                    if previous_source != source:
+                        key_list = [ source, previous_source ]
+                        key_list.sort()
+                        key = '_'.join(key_list)
+                        duplicate_bins.setdefault(key, [])
+                        if package not in duplicate_bins[key]:
+                            duplicate_bins[key].append(package)
+                    if "nfu" in checks:
+                        if package in nfu_entries and \
+                               version != source_versions[source]: # only suggest to remove out-of-date packages
+                            nfu_packages[architecture].append((package,version,source_versions[source]))
+                    
+            packages.close()
+            os.unlink(temp_filename)
+
+    if "obsolete source" in checks:
+        do_obsolete_source(duplicate_bins, bin2source)
+
+    # Distinguish dubious (version numbers match) and 'real' NBS (they don't)
+    dubious_nbs = {}
+    real_nbs = {}
+    for source in nbs.keys():
+        for package in nbs[source].keys():
+            versions = nbs[source][package].keys()
+            versions.sort(apt_pkg.VersionCompare)
+            latest_version = versions.pop()
+            source_version = source_versions.get(source,"0")
+            if apt_pkg.VersionCompare(latest_version, source_version) == 0:
+                add_nbs(dubious_nbs, source, latest_version, package)
+            else:
+                add_nbs(real_nbs, source, latest_version, package)
+
+    if "nviu" in checks:
+        do_nviu()
+
+    if "nbs" in checks:
+        do_nbs(real_nbs)
+
+    ###
+
+    if Options["Mode"] == "full":
+        print "="*75
+        print
+
+    if "nfu" in checks:
+        do_nfu(nfu_packages)
+
+    if "bnb" in checks:
+        print "Unbuilt binary packages"
+        print "-----------------------"
+        print
+        keys = bin_not_built.keys()
+        keys.sort()
+        for source in keys:
+            binaries = bin_not_built[source].keys()
+            binaries.sort()
+            print " o %s: %s" % (source, ", ".join(binaries))
+        print
+
+    if "bms" in checks:
+        print "Built from multiple source packages"
+        print "-----------------------------------"
+        print
+        keys = duplicate_bins.keys()
+        keys.sort()
+        for key in keys:
+            (source_a, source_b) = key.split("_")
+            print " o %s & %s => %s" % (source_a, source_b, ", ".join(duplicate_bins[key]))
+        print
+
+    if "anais" in checks:
+        print "Architecture Not Allowed In Source"
+        print "----------------------------------"
+        print anais_output
+        print
+
+    if "dubious nbs" in checks:
+        do_dubious_nbs(dubious_nbs)
+
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/dak.py b/dak/dak.py
new file mode 100755 (executable)
index 0000000..193b465
--- /dev/null
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+
+"""Wrapper to launch dak functionality"""
+# Copyright (C) 2005, 2006 Anthony Towns <ajt@debian.org>
+# Copyright (C) 2006 James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# well I don't know where you're from but in AMERICA, there's a little
+# thing called "abstinent until proven guilty."
+#  -- http://harrietmiers.blogspot.com/2005/10/wow-i-feel-loved.html
+
+# (if James had a blog, I bet I could find a funny quote in it to use!)
+
+################################################################################
+
+import sys, imp
+import daklib.utils, daklib.extensions
+
+################################################################################
+
+class UserExtension:
+    def __init__(self, user_extension = None):
+        if user_extension:
+            m = imp.load_source("dak_userext", user_extension)
+            d = m.__dict__
+        else:
+            m, d = None, {}
+        self.__dict__["_module"] = m
+        self.__dict__["_d"] = d
+
+    def __getattr__(self, a):
+        if a in self.__dict__: return self.__dict__[a]
+        if a[0] == "_": raise AttributeError, a
+        return self._d.get(a, None)
+
+    def __setattr__(self, a, v):
+        self._d[a] = v
+
+################################################################################
+
+class UserExtension:
+    def __init__(self, user_extension = None):
+        if user_extension:
+            m = imp.load_source("dak_userext", user_extension)
+            d = m.__dict__
+        else:
+            m, d = None, {}
+        self.__dict__["_module"] = m
+        self.__dict__["_d"] = d
+
+    def __getattr__(self, a):
+        if a in self.__dict__: return self.__dict__[a]
+        if a[0] == "_": raise AttributeError, a
+        return self._d.get(a, None)
+
+    def __setattr__(self, a, v):
+        self._d[a] = v
+
+################################################################################
+
+def init():
+    """Setup the list of modules and brief explanation of what they
+    do."""
+
+    functionality = [
+        ("ls",
+         "Show which suites packages are in"),
+        ("override",
+         "Query/change the overrides"),
+        ("check-archive",
+         "Archive sanity checks"),
+        ("queue-report",
+         "Produce a report on NEW and BYHAND packages"),
+        ("show-new",
+         "Output html for packages in NEW"),
+        ("show-deferred",
+         "Output html and symlinks for packages in DEFERRED"),
+
+        ("rm",
+         "Remove packages from suites"),
+
+        ("process-new",
+         "Process NEW and BYHAND packages"),
+        ("process-unchecked",
+         "Process packages in queue/unchecked"),
+        ("process-accepted",
+         "Install packages into the pool"),
+
+        ("make-suite-file-list",
+         "Generate lists of packages per suite for apt-ftparchive"),
+        ("generate-releases",
+         "Generate Release files"),
+        ("generate-index-diffs",
+         "Generate .diff/Index files"),
+        ("clean-suites",
+         "Clean unused/superseded packages from the archive"),
+        ("clean-queues",
+         "Clean cruft from incoming"),
+        ("clean-proposed-updates",
+         "Remove obsolete .changes from proposed-updates"),
+
+        ("transitions",
+         "Manage the release transition file"),
+        ("check-overrides",
+         "Override cruft checks"),
+        ("check-proposed-updates",
+         "Dependency checking for proposed-updates"),
+        ("compare-suites",
+         "Show fixable discrepencies between suites"),
+        ("control-overrides",
+         "Manipulate/list override entries in bulk"),
+        ("control-suite",
+         "Manipulate suites in bulk"),
+        ("cruft-report",
+         "Check for obsolete or duplicated packages"),
+        ("decode-dot-dak",
+         "Display contents of a .dak file"),
+        ("examine-package",
+         "Show information useful for NEW processing"),
+        ("find-null-maintainers",
+         "Check for users with no packages in the archive"),
+        ("import-archive",
+         "Populate SQL database based from an archive tree"),
+        ("import-keyring",
+         "Populate fingerprint/uid table based on a new/updated keyring"),
+        ("import-ldap-fingerprints",
+         "Syncs fingerprint and uid tables with Debian LDAP db"),
+        ("import-users-from-passwd",
+         "Sync PostgreSQL users with passwd file"),
+        ("init-db",
+         "Update the database to match the conf file"),
+        ("init-dirs",
+         "Initial setup of the archive"),
+        ("make-maintainers",
+         "Generates Maintainers file for BTS etc"),
+        ("make-overrides",
+         "Generates override files"),
+        ("mirror-split",
+         "Split the pool/ by architecture groups"),
+        ("poolize",
+         "Move packages from dists/ to pool/"),
+        ("reject-proposed-updates",
+         "Manually reject from proposed-updates"),
+        ("new-security-install",
+         "New way to install a security upload into the archive"),
+        ("split-done",
+         "Split queue/done into a date-based hierarchy"),
+        ("stats",
+         "Generate statistics"),
+        ("symlink-dists",
+         "Generate compatability symlinks from dists/ into pool/"),
+        ]
+    return functionality
+
+################################################################################
+
+def usage(functionality, exit_code=0):
+    """Print a usage message and exit with 'exit_code'."""
+
+    print """Usage: dak COMMAND [...]
+Run DAK commands.  (Will also work if invoked as COMMAND.)
+
+Available commands:"""
+    for (command, description) in functionality:
+        print "  %-23s %s" % (command, description)
+    sys.exit(exit_code)
+
+################################################################################
+
+def main():
+    """Launch dak functionality."""
+
+    Cnf = daklib.utils.get_conf()
+
+    if Cnf.has_key("Dinstall::UserExtensions"):
+        userext = UserExtension(Cnf["Dinstall::UserExtensions"])
+    else:
+        userext = UserExtension()
+
+    functionality = init()
+    modules = [ command for (command, _) in functionality ]
+
+    if len(sys.argv) == 0:
+        daklib.utils.fubar("err, argc == 0? how is that possible?")
+    elif (len(sys.argv) == 1
+          or (len(sys.argv) == 2 and
+              (sys.argv[1] == "--help" or sys.argv[1] == "-h"))):
+        usage(functionality)
+
+    # First see if we were invoked with/as the name of a module
+    cmdname = sys.argv[0]
+    cmdname = cmdname[cmdname.rfind("/")+1:]
+    if cmdname in modules:
+        pass
+    # Otherwise the argument is the module
+    else:
+        cmdname = sys.argv[1]
+        sys.argv = [sys.argv[0] + " " + sys.argv[1]] + sys.argv[2:]
+        if cmdname not in modules:
+            match = []
+            for name in modules:
+                if name.startswith(cmdname):
+                    match.append(name)
+            if len(match) == 1:
+                cmdname = match[0]
+            elif len(match) > 1:
+                daklib.utils.warn("ambiguous command '%s' - could be %s" \
+                           % (cmdname, ", ".join(match)))
+                usage(functionality, 1)
+            else:
+                daklib.utils.warn("unknown command '%s'" % (cmdname))
+                usage(functionality, 1)
+
+    # Invoke the module
+    module = __import__(cmdname.replace("-","_"))
+
+    module.dak_userext = userext
+    userext.dak_module = module
+
+    daklib.extensions.init(cmdname, module, userext)
+    if userext.init is not None: userext.init(cmdname)
+
+    module.main()
+
+################################################################################
+
+if __name__ == "__main__":
+    main()
diff --git a/dak/daklib b/dak/daklib
new file mode 120000 (symlink)
index 0000000..820fad3
--- /dev/null
@@ -0,0 +1 @@
+../daklib
\ No newline at end of file
diff --git a/dak/decode_dot_dak.py b/dak/decode_dot_dak.py
new file mode 100644 (file)
index 0000000..7ea342b
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+# Dump variables from a .dak file to stdout
+# Copyright (C) 2001, 2002, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <elmo> ooooooooooooooohhhhhhhhhhhhhhhhhhhhhhhhh            dddddddddeeeeeeeaaaaaaaarrrrrrrrrrr
+# <elmo> iiiiiiiiiiiii          tttttttttthhhhhhhhiiiiiiiiiiiinnnnnnnnnkkkkkkkkkkkkk              iiiiiiiiiiiiii       mmmmmmmmmmeeeeeeeesssssssssssssssseeeeeeeddd           uuuupppppppppppp       ttttttttthhhhhhhheeeeeeee          xxxxxxxssssssseeeeeeeeettttttttttttt             aaaaaaaarrrrrrrggggggsssssssss
+#
+# ['xset r rate 30 250' bad, mmkay]
+
+################################################################################
+
+import sys
+import apt_pkg
+from daklib import queue
+from daklib import utils
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak decode-dot-dak FILE...
+Dumps the info in .dak FILE(s).
+
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def main():
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Decode-Dot-Dak::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Decode-Dot-Dak::Options::%s" % (i)):
+            Cnf["Decode-Dot-Dak::Options::%s" % (i)] = ""
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Decode-Dot-Dak::Options")
+    if Options["Help"]:
+        usage()
+
+    k = queue.Upload(Cnf)
+    for arg in sys.argv[1:]:
+        arg = utils.validate_changes_file_arg(arg,require_changes=-1)
+        k.pkg.changes_file = arg
+        print "%s:" % (arg)
+        k.init_vars()
+        k.update_vars()
+
+        changes = k.pkg.changes
+        print " Changes:"
+        # Mandatory changes fields
+        for i in [ "source", "version", "maintainer", "urgency", "changedby822",
+                   "changedby2047", "changedbyname", "maintainer822",
+                   "maintainer2047", "maintainername", "maintaineremail",
+                   "fingerprint", "changes" ]:
+            print "  %s: %s" % (i.capitalize(), changes[i])
+            del changes[i]
+        # Mandatory changes lists
+        for i in [ "distribution", "architecture", "closes" ]:
+            print "  %s: %s" % (i.capitalize(), " ".join(changes[i].keys()))
+            del changes[i]
+        # Optional changes fields
+        for i in [ "changed-by", "filecontents", "format", "adv id" ]:
+            if changes.has_key(i):
+                print "  %s: %s" % (i.capitalize(), changes[i])
+                del changes[i]
+        print
+        if changes:
+            utils.warn("changes still has following unrecognised keys: %s" % (changes.keys()))
+
+        dsc = k.pkg.dsc
+        print " Dsc:"
+        for i in [ "source", "version", "maintainer", "fingerprint", "uploaders",
+                   "bts changelog" ]:
+            if dsc.has_key(i):
+                print "  %s: %s" % (i.capitalize(), dsc[i])
+                del dsc[i]
+        print
+        if dsc:
+            utils.warn("dsc still has following unrecognised keys: %s" % (dsc.keys()))
+
+        files = k.pkg.files
+        print " Files:"
+        for f in files.keys():
+            print "  %s:" % (f)
+            for i in [ "package", "version", "architecture", "type", "size",
+                       "md5sum", "sha1sum", "sha256sum", "component", "location id",
+                       "source package", "source version", "maintainer", "dbtype",
+                       "files id", "new", "section", "priority", "pool name" ]:
+                if files[f].has_key(i):
+                    print "   %s: %s" % (i.capitalize(), files[f][i])
+                    del files[f][i]
+            if files[f]:
+                utils.warn("files[%s] still has following unrecognised keys: %s" % (f, files[f].keys()))
+        print
+
+        dsc_files = k.pkg.dsc_files
+        print " Dsc Files:"
+        for f in dsc_files.keys():
+            print "  %s:" % (f)
+            # Mandatory fields
+            for i in [ "size", "md5sum" ]:
+                print "   %s: %s" % (i.capitalize(), dsc_files[f][i])
+                del dsc_files[f][i]
+            # Optional fields
+            for i in [ "files id" ]:
+                if dsc_files[f].has_key(i):
+                    print "   %s: %s" % (i.capitalize(), dsc_files[f][i])
+                    del dsc_files[f][i]
+            if dsc_files[f]:
+                utils.warn("dsc_files[%s] still has following unrecognised keys: %s" % (f, dsc_files[f].keys()))
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/examine_package.py b/dak/examine_package.py
new file mode 100755 (executable)
index 0000000..ae3ec6c
--- /dev/null
@@ -0,0 +1,556 @@
+#!/usr/bin/env python
+
+# Script to automate some parts of checking NEW packages
+# Copyright (C) 2000, 2001, 2002, 2003, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <Omnic> elmo wrote docs?!!?!?!?!?!?!
+# <aj> as if he wasn't scary enough before!!
+# * aj imagines a little red furry toy sitting hunched over a computer
+#   tapping furiously and giggling to himself
+# <aj> eventually he stops, and his heads slowly spins around and you
+#      see this really evil grin and then he sees you, and picks up a
+#      knife from beside the keyboard and throws it at you, and as you
+#      breathe your last breath, he starts giggling again
+# <aj> but i should be telling this to my psychiatrist, not you guys,
+#      right? :)
+
+################################################################################
+
+import errno, os, pg, re, sys, md5
+import apt_pkg, apt_inst
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+re_package = re.compile(r"^(.+?)_.*")
+re_doc_directory = re.compile(r".*/doc/([^/]*).*")
+
+re_contrib = re.compile('^contrib/')
+re_nonfree = re.compile('^non\-free/')
+
+re_arch = re.compile("Architecture: .*")
+re_builddep = re.compile("Build-Depends: .*")
+re_builddepind = re.compile("Build-Depends-Indep: .*")
+
+re_localhost = re.compile("localhost\.localdomain")
+re_version = re.compile('^(.*)\((.*)\)')
+
+re_newlinespace = re.compile('\n')
+re_spacestrip = re.compile('(\s)')
+
+html_escaping = {'"':'&quot;', '&':'&amp;', '<':'&lt;', '>':'&gt;'}
+re_html_escaping = re.compile('|'.join(map(re.escape, html_escaping.keys())))
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+Cnf = utils.get_conf()
+projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+database.init(Cnf, projectB)
+
+printed_copyrights = {}
+
+# default is to not output html.
+use_html = 0
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak examine-package [PACKAGE]...
+Check NEW package(s).
+
+  -h, --help                 show this help and exit
+  -H, --html-output          output html page with inspection result
+  -f, --file-name            filename for the html page
+
+PACKAGE can be a .changes, .dsc, .deb or .udeb filename."""
+
+    sys.exit(exit_code)
+
+################################################################################
+# probably xml.sax.saxutils would work as well
+
+def html_escape(s):
+    return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)
+
+def escape_if_needed(s):
+    if use_html:
+        return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)
+    else:
+        return s
+
+def headline(s, level=2, bodyelement=None):
+    if use_html:
+        if bodyelement:
+            print """<thead>
+                <tr><th colspan="2" class="title" onclick="toggle('%(bodyelement)s', 'table-row-group', 'table-row-group')">%(title)s <span class="toggle-msg">(click to toggle)</span></th></tr>
+              </thead>"""%{"bodyelement":bodyelement,"title":html_escape(s)}
+        else:
+            print "<h%d>%s</h%d>" % (level, html_escape(s), level)
+    else:
+        print "---- %s ----" % (s)
+
+# Colour definitions, 'end' isn't really for use
+
+ansi_colours = {
+  'main': "\033[36m",
+  'contrib': "\033[33m",
+  'nonfree': "\033[31m",
+  'arch': "\033[32m",
+  'end': "\033[0m",
+  'bold': "\033[1m",
+  'maintainer': "\033[32m"}
+
+html_colours = {
+  'main': ('<span style="color: aqua">',"</span>"),
+  'contrib': ('<span style="color: yellow">',"</span>"),
+  'nonfree': ('<span style="color: red">',"</span>"),
+  'arch': ('<span style="color: green">',"</span>"),
+  'bold': ('<span style="font-weight: bold">',"</span>"),
+  'maintainer': ('<span style="color: green">',"</span>")}
+
+def colour_output(s, colour):
+    if use_html:
+        return ("%s%s%s" % (html_colours[colour][0], html_escape(s), html_colours[colour][1]))
+    else:
+        return ("%s%s%s" % (ansi_colours[colour], s, ansi_colours['end']))
+
+def escaped_text(s, strip=False):
+    if use_html:
+        if strip:
+            s = s.strip()
+        return "<pre>%s</pre>" % (s)
+    else:
+        return s
+
+def formatted_text(s, strip=False):
+    if use_html:
+        if strip:
+            s = s.strip()
+        return "<pre>%s</pre>" % (html_escape(s))
+    else:
+        return s
+
+def output_row(s):
+    if use_html:
+        return """<tr><td>"""+s+"""</td></tr>"""
+    else:
+        return s
+
+def format_field(k,v):
+    if use_html:
+        return """<tr><td class="key">%s:</td><td class="val">%s</td></tr>"""%(k,v)
+    else:
+        return "%s: %s"%(k,v)
+
+def foldable_output(title, elementnameprefix, content, norow=False):
+    d = {'elementnameprefix':elementnameprefix}
+    if use_html:
+        print """<div id="%(elementnameprefix)s-wrap"><a name="%(elementnameprefix)s" />
+                   <table class="infobox rfc822">"""%d
+    headline(title, bodyelement="%(elementnameprefix)s-body"%d)
+    if use_html:
+        print """    <tbody id="%(elementnameprefix)s-body" class="infobody">"""%d
+    if norow:
+        print content
+    else:
+        print output_row(content)
+    if use_html:
+        print """</tbody></table></div>"""
+
+################################################################################
+
+def get_depends_parts(depend) :
+    v_match = re_version.match(depend)
+    if v_match:
+        d_parts = { 'name' : v_match.group(1), 'version' : v_match.group(2) }
+    else :
+        d_parts = { 'name' : depend , 'version' : '' }
+    return d_parts
+
+def get_or_list(depend) :
+    or_list = depend.split("|")
+    return or_list
+
+def get_comma_list(depend) :
+    dep_list = depend.split(",")
+    return dep_list
+
+def split_depends (d_str) :
+    # creates a list of lists of dictionaries of depends (package,version relation)
+
+    d_str = re_spacestrip.sub('',d_str)
+    depends_tree = []
+    # first split depends string up amongs comma delimiter
+    dep_list = get_comma_list(d_str)
+    d = 0
+    while d < len(dep_list):
+        # put depends into their own list
+        depends_tree.append([dep_list[d]])
+        d += 1
+    d = 0
+    while d < len(depends_tree):
+        k = 0
+        # split up Or'd depends into a multi-item list
+        depends_tree[d] = get_or_list(depends_tree[d][0])
+        while k < len(depends_tree[d]):
+            # split depends into {package, version relation}
+            depends_tree[d][k] = get_depends_parts(depends_tree[d][k])
+            k += 1
+        d += 1
+    return depends_tree
+
+def read_control (filename):
+    recommends = []
+    depends = []
+    section = ''
+    maintainer = ''
+    arch = ''
+
+    deb_file = utils.open_file(filename)
+    try:
+        extracts = apt_inst.debExtractControl(deb_file)
+        control = apt_pkg.ParseSection(extracts)
+    except:
+        print formatted_text("can't parse control info")
+        deb_file.close()
+        raise
+
+    deb_file.close()
+
+    control_keys = control.keys()
+
+    if control.has_key("Depends"):
+        depends_str = control.Find("Depends")
+        # create list of dependancy lists
+        depends = split_depends(depends_str)
+
+    if control.has_key("Recommends"):
+        recommends_str = control.Find("Recommends")
+        recommends = split_depends(recommends_str)
+
+    if control.has_key("Section"):
+        section_str = control.Find("Section")
+
+        c_match = re_contrib.search(section_str)
+        nf_match = re_nonfree.search(section_str)
+        if c_match :
+            # contrib colour
+            section = colour_output(section_str, 'contrib')
+        elif nf_match :
+            # non-free colour
+            section = colour_output(section_str, 'nonfree')
+        else :
+            # main
+            section = colour_output(section_str, 'main')
+    if control.has_key("Architecture"):
+        arch_str = control.Find("Architecture")
+        arch = colour_output(arch_str, 'arch')
+
+    if control.has_key("Maintainer"):
+        maintainer = control.Find("Maintainer")
+        localhost = re_localhost.search(maintainer)
+        if localhost:
+            #highlight bad email
+            maintainer = colour_output(maintainer, 'maintainer')
+        else:
+            maintainer = escape_if_needed(maintainer)
+
+    return (control, control_keys, section, depends, recommends, arch, maintainer)
+
+def read_changes_or_dsc (filename):
+    dsc = {}
+
+    dsc_file = utils.open_file(filename)
+    try:
+        dsc = utils.parse_changes(filename)
+    except:
+        return formatted_text("can't parse .dsc control info")
+    dsc_file.close()
+
+    filecontents = strip_pgp_signature(filename)
+    keysinorder = []
+    for l in filecontents.split('\n'):
+        m = re.match(r'([-a-zA-Z0-9]*):', l)
+        if m:
+            keysinorder.append(m.group(1))
+
+    for k in dsc.keys():
+        if k in ("build-depends","build-depends-indep"):
+            dsc[k] = create_depends_string(split_depends(dsc[k]))
+        elif k == "architecture":
+            if (dsc["architecture"] != "any"):
+                dsc['architecture'] = colour_output(dsc["architecture"], 'arch')
+        elif k in ("files","changes","description"):
+            if use_html:
+                dsc[k] = formatted_text(dsc[k], strip=True)
+            else:
+                dsc[k] = ('\n'+'\n'.join(map(lambda x: ' '+x, dsc[k].split('\n')))).rstrip()
+        else:
+            dsc[k] = escape_if_needed(dsc[k])
+
+    keysinorder = filter(lambda x: not x.lower().startswith('checksums-'), keysinorder)
+
+    filecontents = '\n'.join(map(lambda x: format_field(x,dsc[x.lower()]), keysinorder))+'\n'
+    return filecontents
+
+def create_depends_string (depends_tree):
+    # just look up unstable for now. possibly pull from .changes later
+    suite = "unstable"
+    result = ""
+    comma_count = 1
+    for l in depends_tree:
+        if (comma_count >= 2):
+            result += ", "
+        or_count = 1
+        for d in l:
+            if (or_count >= 2 ):
+                result += " | "
+            # doesn't do version lookup yet.
+
+            q = projectB.query("SELECT DISTINCT(b.package), b.version, c.name, su.suite_name FROM  binaries b, files fi, location l, component c, bin_associations ba, suite su WHERE b.package='%s' AND b.file = fi.id AND fi.location = l.id AND l.component = c.id AND ba.bin=b.id AND ba.suite = su.id AND su.suite_name='%s' ORDER BY b.version desc" % (d['name'], suite))
+            ql = q.getresult()
+            if ql:
+                i = ql[0]
+
+                adepends = d['name']
+                if d['version'] != '' :
+                    adepends += " (%s)" % (d['version'])
+
+                if i[2] == "contrib":
+                    result += colour_output(adepends, "contrib")
+                elif i[2] == "non-free":
+                    result += colour_output(adepends, "nonfree")
+                else :
+                    result += colour_output(adepends, "main")
+            else:
+                adepends = d['name']
+                if d['version'] != '' :
+                    adepends += " (%s)" % (d['version'])
+                result += colour_output(adepends, "bold")
+            or_count += 1
+        comma_count += 1
+    return result
+
+def output_deb_info(filename):
+    (control, control_keys, section, depends, recommends, arch, maintainer) = read_control(filename)
+
+    if control == '':
+        return formatted_text("no control info")
+    to_print = ""
+    for key in control_keys :
+        if key == 'Depends':
+            field_value = create_depends_string(depends)
+        elif key == 'Recommends':
+            field_value = create_depends_string(recommends)
+        elif key == 'Section':
+            field_value = section
+        elif key == 'Architecture':
+            field_value = arch
+        elif key == 'Maintainer':
+            field_value = maintainer
+        elif key == 'Description':
+            if use_html:
+                field_value = formatted_text(control.Find(key), strip=True)
+            else:
+                desc = control.Find(key)
+                desc = re_newlinespace.sub('\n ', desc)
+                field_value = escape_if_needed(desc)
+        else:
+            field_value = escape_if_needed(control.Find(key))
+        to_print += " "+format_field(key,field_value)+'\n'
+    return to_print
+
+def do_command (command, filename, escaped=0):
+    o = os.popen("%s %s" % (command, filename))
+    if escaped:
+        return escaped_text(o.read())
+    else:
+        return formatted_text(o.read())
+
+def do_lintian (filename):
+    if use_html:
+        return do_command("lintian --show-overrides --color html", filename, 1)
+    else:
+        return do_command("lintian --show-overrides --color always", filename, 1)
+
+def get_copyright (deb_filename):
+    package = re_package.sub(r'\1', deb_filename)
+    o = os.popen("dpkg-deb -c %s | egrep 'usr(/share)?/doc/[^/]*/copyright' | awk '{print $6}' | head -n 1" % (deb_filename))
+    cright = o.read()[:-1]
+
+    if cright == "":
+        return formatted_text("WARNING: No copyright found, please check package manually.")
+
+    doc_directory = re_doc_directory.sub(r'\1', cright)
+    if package != doc_directory:
+        return formatted_text("WARNING: wrong doc directory (expected %s, got %s)." % (package, doc_directory))
+
+    o = os.popen("dpkg-deb --fsys-tarfile %s | tar xvOf - %s 2>/dev/null" % (deb_filename, cright))
+    cright = o.read()
+    copyrightmd5 = md5.md5(cright).hexdigest()
+
+    res = ""
+    if printed_copyrights.has_key(copyrightmd5) and printed_copyrights[copyrightmd5] != "%s (%s)" % (package, deb_filename):
+        res += formatted_text( "NOTE: Copyright is the same as %s.\n\n" % \
+                               (printed_copyrights[copyrightmd5]))
+    else:
+        printed_copyrights[copyrightmd5] = "%s (%s)" % (package, deb_filename)
+    return res+formatted_text(cright)
+
+def check_dsc (dsc_filename):
+    (dsc) = read_changes_or_dsc(dsc_filename)
+    foldable_output(dsc_filename, "dsc", dsc, norow=True)
+    foldable_output("lintian check for %s" % dsc_filename, "source-lintian", do_lintian(dsc_filename))
+
+def check_deb (deb_filename):
+    filename = os.path.basename(deb_filename)
+    packagename = filename.split('_')[0]
+
+    if filename.endswith(".udeb"):
+        is_a_udeb = 1
+    else:
+        is_a_udeb = 0
+
+
+    foldable_output("control file for %s" % (filename), "binary-%s-control"%packagename,
+                    output_deb_info(deb_filename), norow=True)
+
+    if is_a_udeb:
+        foldable_output("skipping lintian check for udeb", "binary-%s-lintian"%packagename,
+                        "")
+    else:
+        foldable_output("lintian check for %s" % (filename), "binary-%s-lintian"%packagename,
+                        do_lintian(deb_filename))
+
+    foldable_output("contents of %s" % (filename), "binary-%s-contents"%packagename,
+                    do_command("dpkg -c", deb_filename))
+
+    if is_a_udeb:
+        foldable_output("skipping copyright for udeb", "binary-%s-copyright"%packagename,
+                        "")
+    else:
+        foldable_output("copyright of %s" % (filename), "binary-%s-copyright"%packagename,
+                        get_copyright(deb_filename))
+
+    foldable_output("file listing of %s" % (filename),  "binary-%s-file-listing"%packagename,
+                    do_command("ls -l", deb_filename))
+
+# Read a file, strip the signature and return the modified contents as
+# a string.
+def strip_pgp_signature (filename):
+    file = utils.open_file (filename)
+    contents = ""
+    inside_signature = 0
+    skip_next = 0
+    for line in file.readlines():
+        if line[:-1] == "":
+            continue
+        if inside_signature:
+            continue
+        if skip_next:
+            skip_next = 0
+            continue
+        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
+            skip_next = 1
+            continue
+        if line.startswith("-----BEGIN PGP SIGNATURE"):
+            inside_signature = 1
+            continue
+        if line.startswith("-----END PGP SIGNATURE"):
+            inside_signature = 0
+            continue
+        contents += line
+    file.close()
+    return contents
+
+def display_changes(changes_filename):
+    changes = read_changes_or_dsc(changes_filename)
+    foldable_output(changes_filename, "changes", changes, norow=True)
+
+def check_changes (changes_filename):
+    display_changes(changes_filename)
+
+    changes = utils.parse_changes (changes_filename)
+    files = utils.build_file_list(changes)
+    for f in files.keys():
+        if f.endswith(".deb") or f.endswith(".udeb"):
+            check_deb(f)
+        if f.endswith(".dsc"):
+            check_dsc(f)
+        # else: => byhand
+
+def main ():
+    global Cnf, projectB, db_files, waste, excluded
+
+#    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Examine-Package::Options::Help"),
+                 ('H',"html-output","Examine-Package::Options::Html-Output"),
+                ]
+    for i in [ "Help", "Html-Output", "partial-html" ]:
+        if not Cnf.has_key("Examine-Package::Options::%s" % (i)):
+            Cnf["Examine-Package::Options::%s" % (i)] = ""
+
+    args = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Examine-Package::Options")
+
+    if Options["Help"]:
+        usage()
+
+    stdout_fd = sys.stdout
+
+    for f in args:
+        try:
+            if not Options["Html-Output"]:
+                # Pipe output for each argument through less
+                less_fd = os.popen("less -R -", 'w', 0)
+                # -R added to display raw control chars for colour
+                sys.stdout = less_fd
+            try:
+                if f.endswith(".changes"):
+                    check_changes(f)
+                elif f.endswith(".deb") or f.endswith(".udeb"):
+                    check_deb(file)
+                elif f.endswith(".dsc"):
+                    check_dsc(f)
+                else:
+                    utils.fubar("Unrecognised file type: '%s'." % (f))
+            finally:
+                if not Options["Html-Output"]:
+                    # Reset stdout here so future less invocations aren't FUBAR
+                    less_fd.close()
+                    sys.stdout = stdout_fd
+        except IOError, e:
+            if errno.errorcode[e.errno] == 'EPIPE':
+                utils.warn("[examine-package] Caught EPIPE; skipping.")
+                pass
+            else:
+                raise
+        except KeyboardInterrupt:
+            utils.warn("[examine-package] Caught C-c; skipping.")
+            pass
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/find_null_maintainers.py b/dak/find_null_maintainers.py
new file mode 100755 (executable)
index 0000000..652edfd
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+
+# Check for users with no packages in the archive
+# Copyright (C) 2003, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import ldap, pg, sys, time
+import apt_pkg
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak find-null-maintainers
+Checks for users with no packages in the archive
+
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def get_ldap_value(entry, value):
+    ret = entry.get(value)
+    if not ret:
+        return ""
+    else:
+        # FIXME: what about > 0 ?
+        return ret[0]
+
+def main():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Find-Null-Maintainers::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Find-Null-Maintainers::Options::%s" % (i)):
+            Cnf["Find-Null-Maintainers::Options::%s" % (i)] = ""
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Find-Null-Maintainers::Options")
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+
+    before = time.time()
+    sys.stderr.write("[Getting info from the LDAP server...")
+    LDAPDn = Cnf["Import-LDAP-Fingerprints::LDAPDn"]
+    LDAPServer = Cnf["Import-LDAP-Fingerprints::LDAPServer"]
+    l = ldap.open(LDAPServer)
+    l.simple_bind_s("","")
+    Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
+                       "(&(keyfingerprint=*)(gidnumber=%s))" % (Cnf["Import-Users-From-Passwd::ValidGID"]),
+                       ["uid", "cn", "mn", "sn", "createTimestamp"])
+    sys.stderr.write("done. (%d seconds)]\n" % (int(time.time()-before)))
+
+
+    db_uid = {}
+    db_unstable_uid = {}
+
+    before = time.time()
+    sys.stderr.write("[Getting UID info for entire archive...")
+    q = projectB.query("SELECT DISTINCT u.uid FROM uid u, fingerprint f WHERE f.uid = u.id;")
+    sys.stderr.write("done. (%d seconds)]\n" % (int(time.time()-before)))
+    for i in q.getresult():
+        db_uid[i[0]] = ""
+
+    before = time.time()
+    sys.stderr.write("[Getting UID info for unstable...")
+    q = projectB.query("""
+SELECT DISTINCT u.uid FROM suite su, src_associations sa, source s, fingerprint f, uid u
+ WHERE f.uid = u.id AND sa.source = s.id AND sa.suite = su.id
+   AND su.suite_name = 'unstable' AND s.sig_fpr = f.id
+UNION
+SELECT DISTINCT u.uid FROM suite su, bin_associations ba, binaries b, fingerprint f, uid u
+ WHERE f.uid = u.id AND ba.bin = b.id AND ba.suite = su.id
+   AND su.suite_name = 'unstable' AND b.sig_fpr = f.id""")
+    sys.stderr.write("done. (%d seconds)]\n" % (int(time.time()-before)))
+    for i in q.getresult():
+        db_unstable_uid[i[0]] = ""
+
+    now = time.time()
+
+    for i in Attrs:
+        entry = i[1]
+        uid = entry["uid"][0]
+        created = time.mktime(time.strptime(entry["createTimestamp"][0][:8], '%Y%m%d'))
+        diff = now - created
+        # 31536000 is 1 year in seconds, i.e. 60 * 60 * 24 * 365
+        if diff < 31536000 / 2:
+            when = "Less than 6 months ago"
+        elif diff < 31536000:
+            when = "Less than 1 year ago"
+        elif diff < 31536000 * 1.5:
+            when = "Less than 18 months ago"
+        elif diff < 31536000 * 2:
+            when = "Less than 2 years ago"
+        elif diff < 31536000 * 3:
+            when = "Less than 3 years ago"
+        else:
+            when = "More than 3 years ago"
+        name = " ".join([get_ldap_value(entry, "cn"),
+                         get_ldap_value(entry, "mn"),
+                         get_ldap_value(entry, "sn")])
+        if not db_uid.has_key(uid):
+            print "NONE %s (%s) %s" % (uid, name, when)
+        else:
+            if not db_unstable_uid.has_key(uid):
+                print "NOT_UNSTABLE %s (%s) %s" % (uid, name, when)
+
+############################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/generate_index_diffs.py b/dak/generate_index_diffs.py
new file mode 100755 (executable)
index 0000000..286c1eb
--- /dev/null
@@ -0,0 +1,391 @@
+#!/usr/bin/env python
+
+###########################################################
+# generates partial package updates list
+
+# idea and basic implementation by Anthony, some changes by Andreas
+# parts are stolen from 'dak generate-releases'
+#
+# Copyright (C) 2004, 2005, 2006  Anthony Towns <aj@azure.humbug.org.au>
+# Copyright (C) 2004, 2005  Andreas Barth <aba@not.so.argh.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+# < elmo> bah, don't bother me with annoying facts
+# < elmo> I was on a roll
+
+
+################################################################################
+
+import sys, os, tempfile
+import apt_pkg
+from daklib import utils
+
+################################################################################
+
+projectB = None
+Cnf = None
+Logger = None
+Options = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak generate-index-diffs [OPTIONS] [suites]
+Write out ed-style diffs to Packages/Source lists
+
+  -h, --help            show this help and exit
+  -c                    give the canonical path of the file
+  -p                    name for the patch (defaults to current time)
+  -n                    take no action
+    """
+    sys.exit(exit_code)
+
+
+def tryunlink(file):
+    try:
+        os.unlink(file)
+    except OSError:
+        print "warning: removing of %s denied" % (file)
+
+def smartstat(file):
+    for ext in ["", ".gz", ".bz2"]:
+        if os.path.isfile(file + ext):
+            return (ext, os.stat(file + ext))
+    return (None, None)
+
+def smartlink(f, t):
+    if os.path.isfile(f):
+        os.link(f,t)
+    elif os.path.isfile("%s.gz" % (f)):
+        os.system("gzip -d < %s.gz > %s" % (f, t))
+    elif os.path.isfile("%s.bz2" % (f)):
+        os.system("bzip2 -d < %s.bz2 > %s" % (f, t))
+    else:
+        print "missing: %s" % (f)
+        raise IOError, f
+
+def smartopen(file):
+    if os.path.isfile(file):
+        f = open(file, "r")
+    elif os.path.isfile("%s.gz" % file):
+        f = create_temp_file(os.popen("zcat %s.gz" % file, "r"))
+    elif os.path.isfile("%s.bz2" % file):
+        f = create_temp_file(os.popen("bzcat %s.bz2" % file, "r"))
+    else:
+        f = None
+    return f
+
+def pipe_file(f, t):
+    f.seek(0)
+    while 1:
+        l = f.read()
+        if not l: break
+        t.write(l)
+    t.close()
+
+class Updates:
+    def __init__(self, readpath = None, max = 14):
+        self.can_path = None
+        self.history = {}
+        self.history_order = []
+        self.max = max
+        self.readpath = readpath
+        self.filesizesha1 = None
+
+        if readpath:
+            try:
+                f = open(readpath + "/Index")
+                x = f.readline()
+
+                def read_hashs(ind, f, self, x=x):
+                    while 1:
+                        x = f.readline()
+                        if not x or x[0] != " ": break
+                        l = x.split()
+                        if not self.history.has_key(l[2]):
+                            self.history[l[2]] = [None,None]
+                            self.history_order.append(l[2])
+                        self.history[l[2]][ind] = (l[0], int(l[1]))
+                    return x
+
+                while x:
+                    l = x.split()
+
+                    if len(l) == 0:
+                        x = f.readline()
+                        continue
+
+                    if l[0] == "SHA1-History:":
+                        x = read_hashs(0,f,self)
+                        continue
+
+                    if l[0] == "SHA1-Patches:":
+                        x = read_hashs(1,f,self)
+                        continue
+
+                    if l[0] == "Canonical-Name:" or l[0]=="Canonical-Path:":
+                        self.can_path = l[1]
+
+                    if l[0] == "SHA1-Current:" and len(l) == 3:
+                        self.filesizesha1 = (l[1], int(l[2]))
+
+                    x = f.readline()
+
+            except IOError:
+                0
+
+    def dump(self, out=sys.stdout):
+        if self.can_path:
+            out.write("Canonical-Path: %s\n" % (self.can_path))
+
+        if self.filesizesha1:
+            out.write("SHA1-Current: %s %7d\n" % (self.filesizesha1))
+
+        hs = self.history
+        l = self.history_order[:]
+
+        cnt = len(l)
+        if cnt > self.max:
+            for h in l[:cnt-self.max]:
+                tryunlink("%s/%s.gz" % (self.readpath, h))
+                del hs[h]
+            l = l[cnt-self.max:]
+            self.history_order = l[:]
+
+        out.write("SHA1-History:\n")
+        for h in l:
+            out.write(" %s %7d %s\n" % (hs[h][0][0], hs[h][0][1], h))
+        out.write("SHA1-Patches:\n")
+        for h in l:
+            out.write(" %s %7d %s\n" % (hs[h][1][0], hs[h][1][1], h))
+
+def create_temp_file(r):
+    f = tempfile.TemporaryFile()
+    while 1:
+        x = r.readline()
+        if not x: break
+        f.write(x)
+    r.close()
+    del x,r
+    f.flush()
+    f.seek(0)
+    return f
+
+def sizesha1(f):
+    size = os.fstat(f.fileno())[6]
+    f.seek(0)
+    sha1sum = apt_pkg.sha1sum(f)
+    return (sha1sum, size)
+
+def genchanges(Options, outdir, oldfile, origfile, maxdiffs = 14):
+    if Options.has_key("NoAct"):
+        return
+
+    patchname = Options["PatchName"]
+
+    # origfile = /path/to/Packages
+    # oldfile  = ./Packages
+    # newfile  = ./Packages.tmp
+    # difffile = outdir/patchname
+    # index   => outdir/Index
+
+    # (outdir, oldfile, origfile) = argv
+
+    newfile = oldfile + ".new"
+    difffile = "%s/%s" % (outdir, patchname)
+
+    upd = Updates(outdir, int(maxdiffs))
+    (oldext, oldstat) = smartstat(oldfile)
+    (origext, origstat) = smartstat(origfile)
+    if not origstat:
+        print "%s: doesn't exist" % (origfile)
+        return
+    if not oldstat:
+        print "%s: initial run" % (origfile)
+        os.link(origfile + origext, oldfile + origext)
+        return
+
+    if oldstat[1:3] == origstat[1:3]:
+        print "%s: hardlink unbroken, assuming unchanged" % (origfile)
+        return
+
+    oldf = smartopen(oldfile)
+    oldsizesha1 = sizesha1(oldf)
+
+    # should probably early exit if either of these checks fail
+    # alternatively (optionally?) could just trim the patch history
+
+    if upd.filesizesha1:
+        if upd.filesizesha1 != oldsizesha1:
+            print "warning: old file seems to have changed! %s %s => %s %s" % (upd.filesizesha1 + oldsizesha1)
+
+    # XXX this should be usable now
+    #
+    #for d in upd.history.keys():
+    #    df = smartopen("%s/%s" % (outdir,d))
+    #    act_sha1size = sizesha1(df)
+    #    df.close()
+    #    exp_sha1size = upd.history[d][1]
+    #    if act_sha1size != exp_sha1size:
+    #        print "patch file %s seems to have changed! %s %s => %s %s" % \
+    #            (d,) + exp_sha1size + act_sha1size
+
+    if Options.has_key("CanonicalPath"): upd.can_path=Options["CanonicalPath"]
+
+    if os.path.exists(newfile): os.unlink(newfile)
+    smartlink(origfile, newfile)
+    newf = open(newfile, "r")
+    newsizesha1 = sizesha1(newf)
+    newf.close()
+
+    if newsizesha1 == oldsizesha1:
+        os.unlink(newfile)
+        oldf.close()
+        print "%s: unchanged" % (origfile)
+    else:
+        if not os.path.isdir(outdir): os.mkdir(outdir)
+        w = os.popen("diff --ed - %s | gzip -c -9 > %s.gz" %
+                         (newfile, difffile), "w")
+        pipe_file(oldf, w)
+        oldf.close()
+
+        difff = smartopen(difffile)
+        difsizesha1 = sizesha1(difff)
+        difff.close()
+
+        upd.history[patchname] = (oldsizesha1, difsizesha1)
+        upd.history_order.append(patchname)
+
+        upd.filesizesha1 = newsizesha1
+
+        os.unlink(oldfile + oldext)
+        os.link(origfile + origext, oldfile + origext)
+        os.unlink(newfile)
+
+        f = open(outdir + "/Index", "w")
+        upd.dump(f)
+        f.close()
+
+
+def main():
+    global Cnf, Options, Logger
+
+    os.umask(0002)
+
+    Cnf = utils.get_conf()
+    Arguments = [ ('h', "help", "Generate-Index-Diffs::Options::Help"),
+                  ('c', None, "Generate-Index-Diffs::Options::CanonicalPath", "hasArg"),
+                  ('p', "patchname", "Generate-Index-Diffs::Options::PatchName", "hasArg"),
+                  ('r', "rootdir", "Generate-Index-Diffs::Options::RootDir", "hasArg"),
+                  ('d', "tmpdir", "Generate-Index-Diffs::Options::TempDir", "hasArg"),
+                  ('m', "maxdiffs", "Generate-Index-Diffs::Options::MaxDiffs", "hasArg"),
+                  ('n', "n-act", "Generate-Index-Diffs::Options::NoAct"),
+                ]
+    suites = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Generate-Index-Diffs::Options")
+    if Options.has_key("Help"): usage()
+
+    maxdiffs = Options.get("MaxDiffs::Default", "14")
+    maxpackages = Options.get("MaxDiffs::Packages", maxdiffs)
+    maxcontents = Options.get("MaxDiffs::Contents", maxdiffs)
+    maxsources = Options.get("MaxDiffs::Sources", maxdiffs)
+
+    if not Options.has_key("PatchName"):
+        format = "%Y-%m-%d-%H%M.%S"
+        i,o = os.popen2("date +%s" % (format))
+        i.close()
+        Options["PatchName"] = o.readline()[:-1]
+        o.close()
+
+    AptCnf = apt_pkg.newConfiguration()
+    apt_pkg.ReadConfigFileISC(AptCnf,utils.which_apt_conf_file())
+
+    if Options.has_key("RootDir"): Cnf["Dir::Root"] = Options["RootDir"]
+
+    if not suites:
+        suites = Cnf.SubTree("Suite").List()
+
+    for suite in suites:
+        print "Processing: " + suite
+        SuiteBlock = Cnf.SubTree("Suite::" + suite)
+
+        if SuiteBlock.has_key("Untouchable"):
+            print "Skipping: " + suite + " (untouchable)"
+            continue
+
+        suite = suite.lower()
+
+        architectures = SuiteBlock.ValueList("Architectures")
+
+        if SuiteBlock.has_key("Components"):
+            components = SuiteBlock.ValueList("Components")
+        else:
+            components = []
+
+        suite_suffix = Cnf.Find("Dinstall::SuiteSuffix")
+        if components and suite_suffix:
+            longsuite = suite + "/" + suite_suffix
+        else:
+            longsuite = suite
+
+        tree = SuiteBlock.get("Tree", "dists/%s" % (longsuite))
+
+        if AptCnf.has_key("tree::%s" % (tree)):
+            sections = AptCnf["tree::%s::Sections" % (tree)].split()
+        elif AptCnf.has_key("bindirectory::%s" % (tree)):
+            sections = AptCnf["bindirectory::%s::Sections" % (tree)].split()
+        else:
+            aptcnf_filename = os.path.basename(utils.which_apt_conf_file())
+            print "ALERT: suite %s not in %s, nor untouchable!" % (suite, aptcnf_filename)
+            continue
+
+        for architecture in architectures:
+            if architecture == "all":
+                continue
+
+            if architecture != "source":
+                # Process Contents
+                file = "%s/Contents-%s" % (Cnf["Dir::Root"] + tree,
+                        architecture)
+                storename = "%s/%s_contents_%s" % (Options["TempDir"], suite, architecture)
+                genchanges(Options, file + ".diff", storename, file, \
+                  Cnf.get("Suite::%s::Generate-Index-Diffs::MaxDiffs::Contents" % (suite), maxcontents))
+
+            # use sections instead of components since dak.conf
+            # treats "foo/bar main" as suite "foo", suitesuffix "bar" and
+            # component "bar/main". suck.
+
+            for component in sections:
+                if architecture == "source":
+                    longarch = architecture
+                    packages = "Sources"
+                    maxsuite = maxsources
+                else:
+                    longarch = "binary-%s"% (architecture)
+                    packages = "Packages"
+                    maxsuite = maxpackages
+
+                file = "%s/%s/%s/%s" % (Cnf["Dir::Root"] + tree,
+                           component, longarch, packages)
+                storename = "%s/%s_%s_%s" % (Options["TempDir"], suite, component, architecture)
+                genchanges(Options, file + ".diff", storename, file, \
+                  Cnf.get("Suite::%s::Generate-Index-Diffs::MaxDiffs::%s" % (suite, packages), maxsuite))
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/generate_releases.py b/dak/generate_releases.py
new file mode 100755 (executable)
index 0000000..0083119
--- /dev/null
@@ -0,0 +1,353 @@
+#!/usr/bin/env python
+
+# Create all the Release files
+
+# Copyright (C) 2001, 2002, 2006  Anthony Towns <ajt@debian.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#   ``Bored now''
+
+################################################################################
+
+import sys, os, popen2, tempfile, stat, time, pg
+import apt_pkg
+from daklib import utils
+from daklib.dak_exceptions import *
+
+################################################################################
+
+Cnf = None
+projectB = None
+out = None
+AptCnf = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak generate-releases [OPTION]... [SUITE]...
+Generate Release files (for SUITE).
+
+  -h, --help                 show this help and exit
+  -a, --apt-conf FILE        use FILE instead of default apt.conf
+  -f, --force-touch          ignore Untouchable directives in dak.conf
+
+If no SUITE is given Release files are generated for all suites."""
+
+    sys.exit(exit_code)
+
+################################################################################
+
+def add_tiffani (files, path, indexstem):
+    index = "%s.diff/Index" % (indexstem)
+    filepath = "%s/%s" % (path, index)
+    if os.path.exists(filepath):
+        #print "ALERT: there was a tiffani file %s" % (filepath)
+        files.append(index)
+
+def compressnames (tree,type,file):
+    compress = AptCnf.get("%s::%s::Compress" % (tree,type), AptCnf.get("Default::%s::Compress" % (type), ". gzip"))
+    result = []
+    cl = compress.split()
+    uncompress = ("." not in cl)
+    for mode in compress.split():
+        if mode == ".":
+            result.append(file)
+        elif mode == "gzip":
+            if uncompress:
+                result.append("<zcat/.gz>" + file)
+                uncompress = 0
+            result.append(file + ".gz")
+        elif mode == "bzip2":
+            if uncompress:
+                result.append("<bzcat/.bz2>" + file)
+                uncompress = 0
+            result.append(file + ".bz2")
+    return result
+
+def create_temp_file (cmd):
+    f = tempfile.TemporaryFile()
+    r = popen2.popen2(cmd)
+    r[1].close()
+    r = r[0]
+    size = 0
+    while 1:
+        x = r.readline()
+        if not x:
+            r.close()
+            del x,r
+            break
+        f.write(x)
+        size += len(x)
+    f.flush()
+    f.seek(0)
+    return (size, f)
+
+def print_md5sha_files (tree, files, hashop):
+    path = Cnf["Dir::Root"] + tree + "/"
+    for name in files:
+        try:
+            if name[0] == "<":
+                j = name.index("/")
+                k = name.index(">")
+                (cat, ext, name) = (name[1:j], name[j+1:k], name[k+1:])
+                (size, file_handle) = create_temp_file("%s %s%s%s" %
+                    (cat, path, name, ext))
+            else:
+                size = os.stat(path + name)[stat.ST_SIZE]
+                file_handle = utils.open_file(path + name)
+        except CantOpenError:
+            print "ALERT: Couldn't open " + path + name
+        else:
+            hash = hashop(file_handle)
+            file_handle.close()
+            out.write(" %s %8d %s\n" % (hash, size, name))
+
+def print_md5_files (tree, files):
+    print_md5sha_files (tree, files, apt_pkg.md5sum)
+
+def print_sha1_files (tree, files):
+    print_md5sha_files (tree, files, apt_pkg.sha1sum)
+
+def print_sha256_files (tree, files):
+    print_md5sha_files (tree, files, apt_pkg.sha256sum)
+
+################################################################################
+
+def main ():
+    global Cnf, AptCnf, projectB, out
+    out = sys.stdout
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Generate-Releases::Options::Help"),
+                 ('a',"apt-conf","Generate-Releases::Options::Apt-Conf", "HasArg"),
+                 ('f',"force-touch","Generate-Releases::Options::Force-Touch"),
+                ]
+    for i in [ "help", "apt-conf", "force-touch" ]:
+        if not Cnf.has_key("Generate-Releases::Options::%s" % (i)):
+            Cnf["Generate-Releases::Options::%s" % (i)] = ""
+
+    suites = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Generate-Releases::Options")
+
+    if Options["Help"]:
+        usage()
+
+    if not Options["Apt-Conf"]:
+        Options["Apt-Conf"] = utils.which_apt_conf_file()
+
+    AptCnf = apt_pkg.newConfiguration()
+    apt_pkg.ReadConfigFileISC(AptCnf, Options["Apt-Conf"])
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+
+    if not suites:
+        suites = Cnf.SubTree("Suite").List()
+
+    for suite in suites:
+        print "Processing: " + suite
+        SuiteBlock = Cnf.SubTree("Suite::" + suite)
+
+        if SuiteBlock.has_key("Untouchable") and not Options["Force-Touch"]:
+            print "Skipping: " + suite + " (untouchable)"
+            continue
+
+        suite = suite.lower()
+
+        origin = SuiteBlock["Origin"]
+        label = SuiteBlock.get("Label", origin)
+        codename = SuiteBlock.get("CodeName", "")
+
+        version = ""
+        description = ""
+
+        q = projectB.query("SELECT version, description FROM suite WHERE suite_name = '%s'" % (suite))
+        qs = q.getresult()
+        if len(qs) == 1:
+            if qs[0][0] != "-": version = qs[0][0]
+            if qs[0][1]: description = qs[0][1]
+
+        if SuiteBlock.has_key("NotAutomatic"):
+            notautomatic = "yes"
+        else:
+            notautomatic = ""
+
+        if SuiteBlock.has_key("Components"):
+            components = SuiteBlock.ValueList("Components")
+        else:
+            components = []
+
+        suite_suffix = Cnf.Find("Dinstall::SuiteSuffix")
+        if components and suite_suffix:
+            longsuite = suite + "/" + suite_suffix
+        else:
+            longsuite = suite
+
+        tree = SuiteBlock.get("Tree", "dists/%s" % (longsuite))
+
+        if AptCnf.has_key("tree::%s" % (tree)):
+            pass
+        elif AptCnf.has_key("bindirectory::%s" % (tree)):
+            pass
+        else:
+            aptcnf_filename = os.path.basename(utils.which_apt_conf_file())
+            print "ALERT: suite %s not in %s, nor untouchable!" % (suite, aptcnf_filename)
+            continue
+
+        print Cnf["Dir::Root"] + tree + "/Release"
+        out = open(Cnf["Dir::Root"] + tree + "/Release", "w")
+
+        out.write("Origin: %s\n" % (origin))
+        out.write("Label: %s\n" % (label))
+        out.write("Suite: %s\n" % (suite))
+        if version != "":
+            out.write("Version: %s\n" % (version))
+        if codename != "":
+            out.write("Codename: %s\n" % (codename))
+        out.write("Date: %s\n" % (time.strftime("%a, %d %b %Y %H:%M:%S UTC", time.gmtime(time.time()))))
+
+        if SuiteBlock.has_key("ValidTime"):
+            validtime=float(SuiteBlock["ValidTime"])
+            out.write("Valid-Until: %s\n" % (time.strftime("%a, %d %b %Y %H:%M:%S UTC", time.gmtime(time.time()+validtime))))
+
+        if notautomatic != "":
+            out.write("NotAutomatic: %s\n" % (notautomatic))
+        out.write("Architectures: %s\n" % (" ".join(filter(utils.real_arch, SuiteBlock.ValueList("Architectures")))))
+        if components:
+            out.write("Components: %s\n" % (" ".join(components)))
+
+        if description:
+            out.write("Description: %s\n" % (description))
+
+        files = []
+
+        if AptCnf.has_key("tree::%s" % (tree)):
+            for sec in AptCnf["tree::%s::Sections" % (tree)].split():
+                for arch in AptCnf["tree::%s::Architectures" % (tree)].split():
+                    if arch == "source":
+                        filepath = "%s/%s/Sources" % (sec, arch)
+                        for file in compressnames("tree::%s" % (tree), "Sources", filepath):
+                            files.append(file)
+                        add_tiffani(files, Cnf["Dir::Root"] + tree, filepath)
+                    else:
+                        disks = "%s/disks-%s" % (sec, arch)
+                        diskspath = Cnf["Dir::Root"]+tree+"/"+disks
+                        if os.path.exists(diskspath):
+                            for dir in os.listdir(diskspath):
+                                if os.path.exists("%s/%s/md5sum.txt" % (diskspath, dir)):
+                                    files.append("%s/%s/md5sum.txt" % (disks, dir))
+
+                        filepath = "%s/binary-%s/Packages" % (sec, arch)
+                        for file in compressnames("tree::%s" % (tree), "Packages", filepath):
+                            files.append(file)
+                        add_tiffani(files, Cnf["Dir::Root"] + tree, filepath)
+
+                    if arch == "source":
+                        rel = "%s/%s/Release" % (sec, arch)
+                    else:
+                        rel = "%s/binary-%s/Release" % (sec, arch)
+                    relpath = Cnf["Dir::Root"]+tree+"/"+rel
+
+                    try:
+                        if os.access(relpath, os.F_OK):
+                            if os.stat(relpath).st_nlink > 1:
+                                os.unlink(relpath)
+                        release = open(relpath, "w")
+                        #release = open(longsuite.replace("/","_") + "_" + arch + "_" + sec + "_Release", "w")
+                    except IOError:
+                        utils.fubar("Couldn't write to " + relpath)
+
+                    release.write("Archive: %s\n" % (suite))
+                    if version != "":
+                        release.write("Version: %s\n" % (version))
+                    if suite_suffix:
+                        release.write("Component: %s/%s\n" % (suite_suffix,sec))
+                    else:
+                        release.write("Component: %s\n" % (sec))
+                    release.write("Origin: %s\n" % (origin))
+                    release.write("Label: %s\n" % (label))
+                    if notautomatic != "":
+                        release.write("NotAutomatic: %s\n" % (notautomatic))
+                    release.write("Architecture: %s\n" % (arch))
+                    release.close()
+                    files.append(rel)
+
+            if AptCnf.has_key("tree::%s/main" % (tree)):
+                for dis in ["main", "contrib", "non-free"]:
+                    if not AptCnf.has_key("tree::%s/%s" % (tree, dis)): continue
+                    sec = AptCnf["tree::%s/%s::Sections" % (tree,dis)].split()[0]
+                    if sec != "debian-installer":
+                        print "ALERT: weird non debian-installer section in %s" % (tree)
+
+                    for arch in AptCnf["tree::%s/%s::Architectures" % (tree,dis)].split():
+                        if arch != "source":  # always true
+                            for file in compressnames("tree::%s/%s" % (tree,dis),
+                                "Packages",
+                                "%s/%s/binary-%s/Packages" % (dis, sec, arch)):
+                                files.append(file)
+            elif AptCnf.has_key("tree::%s::FakeDI" % (tree)):
+                usetree = AptCnf["tree::%s::FakeDI" % (tree)]
+                sec = AptCnf["tree::%s/main::Sections" % (usetree)].split()[0]
+                if sec != "debian-installer":
+                    print "ALERT: weird non debian-installer section in %s" % (usetree)
+
+                for arch in AptCnf["tree::%s/main::Architectures" % (usetree)].split():
+                    if arch != "source":  # always true
+                        for file in compressnames("tree::%s/main" % (usetree), "Packages", "main/%s/binary-%s/Packages" % (sec, arch)):
+                            files.append(file)
+
+        elif AptCnf.has_key("bindirectory::%s" % (tree)):
+            for file in compressnames("bindirectory::%s" % (tree), "Packages", AptCnf["bindirectory::%s::Packages" % (tree)]):
+                files.append(file.replace(tree+"/","",1))
+            for file in compressnames("bindirectory::%s" % (tree), "Sources", AptCnf["bindirectory::%s::Sources" % (tree)]):
+                files.append(file.replace(tree+"/","",1))
+        else:
+            print "ALERT: no tree/bindirectory for %s" % (tree)
+
+        out.write("MD5Sum:\n")
+        print_md5_files(tree, files)
+        out.write("SHA1:\n")
+        print_sha1_files(tree, files)
+        out.write("SHA256:\n")
+        print_sha256_files(tree, files)
+
+        out.close()
+        if Cnf.has_key("Dinstall::SigningKeyring"):
+            keyring = "--secret-keyring \"%s\"" % Cnf["Dinstall::SigningKeyring"]
+            if Cnf.has_key("Dinstall::SigningPubKeyring"):
+                keyring += " --keyring \"%s\"" % Cnf["Dinstall::SigningPubKeyring"]
+
+            arguments = "--no-options --batch --no-tty --armour"
+            if Cnf.has_key("Dinstall::SigningKeyIds"):
+                signkeyids = Cnf["Dinstall::SigningKeyIds"].split()
+            else:
+                signkeyids = [""]
+
+            dest = Cnf["Dir::Root"] + tree + "/Release.gpg"
+            if os.path.exists(dest):
+                os.unlink(dest)
+
+            for keyid in signkeyids:
+                if keyid != "": defkeyid = "--default-key %s" % keyid
+                else: defkeyid = ""
+                os.system("gpg %s %s %s --detach-sign <%s >>%s" %
+                        (keyring, defkeyid, arguments,
+                        Cnf["Dir::Root"] + tree + "/Release", dest))
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/import_archive.py b/dak/import_archive.py
new file mode 100755 (executable)
index 0000000..4432ff1
--- /dev/null
@@ -0,0 +1,622 @@
+#!/usr/bin/env python
+
+# Populate the DB
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+###############################################################################
+
+# 04:36|<aj> elmo: you're making me waste 5 seconds per architecture!!!!!! YOU BASTARD!!!!!
+
+###############################################################################
+
+# This code is a horrible mess for two reasons:
+
+#   (o) For Debian's usage, it's doing something like 160k INSERTs,
+#   even on auric, that makes the program unusable unless we get
+#   involed in sorts of silly optimization games (local dicts to avoid
+#   redundant SELECTS, using COPY FROM rather than INSERTS etc.)
+
+#   (o) It's very site specific, because I don't expect to use this
+#   script again in a hurry, and I don't want to spend any more time
+#   on it than absolutely necessary.
+
+###############################################################################
+
+import commands, os, pg, re, sys, time
+import apt_pkg
+from daklib import database
+from daklib import utils
+from daklib.dak_exceptions import *
+
+###############################################################################
+
+re_arch_from_filename = re.compile(r"binary-[^/]+")
+
+###############################################################################
+
+Cnf = None
+projectB = None
+files_id_cache = {}
+source_cache = {}
+arch_all_cache = {}
+binary_cache = {}
+location_path_cache = {}
+#
+files_id_serial = 0
+source_id_serial = 0
+src_associations_id_serial = 0
+dsc_files_id_serial = 0
+files_query_cache = None
+source_query_cache = None
+src_associations_query_cache = None
+dsc_files_query_cache = None
+orig_tar_gz_cache = {}
+#
+binaries_id_serial = 0
+binaries_query_cache = None
+bin_associations_id_serial = 0
+bin_associations_query_cache = None
+#
+source_cache_for_binaries = {}
+reject_message = ""
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak import-archive
+Initializes a projectB database from an existing archive
+
+  -a, --action              actually perform the initalization
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+###############################################################################
+
+def reject (str, prefix="Rejected: "):
+    global reject_message
+    if str:
+        reject_message += prefix + str + "\n"
+
+###############################################################################
+
+def check_signature (filename):
+    if not utils.re_taint_free.match(os.path.basename(filename)):
+        reject("!!WARNING!! tainted filename: '%s'." % (filename))
+        return None
+
+    status_read, status_write = os.pipe()
+    cmd = "gpgv --status-fd %s %s %s" \
+          % (status_write, utils.gpg_keyring_args(), filename)
+    (output, status, exit_status) = utils.gpgv_get_status_output(cmd, status_read, status_write)
+
+    # Process the status-fd output
+    keywords = {}
+    bad = internal_error = ""
+    for line in status.split('\n'):
+        line = line.strip()
+        if line == "":
+            continue
+        split = line.split()
+        if len(split) < 2:
+            internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
+            continue
+        (gnupg, keyword) = split[:2]
+        if gnupg != "[GNUPG:]":
+            internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
+            continue
+        args = split[2:]
+        if keywords.has_key(keyword) and keyword != "NODATA" and keyword != "SIGEXPIRED":
+            internal_error += "found duplicate status token ('%s').\n" % (keyword)
+            continue
+        else:
+            keywords[keyword] = args
+
+    # If we failed to parse the status-fd output, let's just whine and bail now
+    if internal_error:
+        reject("internal error while performing signature check on %s." % (filename))
+        reject(internal_error, "")
+        reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
+        return None
+
+    # Now check for obviously bad things in the processed output
+    if keywords.has_key("SIGEXPIRED"):
+        utils.warn("%s: signing key has expired." % (filename))
+    if keywords.has_key("KEYREVOKED"):
+        reject("key used to sign %s has been revoked." % (filename))
+        bad = 1
+    if keywords.has_key("BADSIG"):
+        reject("bad signature on %s." % (filename))
+        bad = 1
+    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
+        reject("failed to check signature on %s." % (filename))
+        bad = 1
+    if keywords.has_key("NO_PUBKEY"):
+        args = keywords["NO_PUBKEY"]
+        if len(args) < 1:
+            reject("internal error while checking signature on %s." % (filename))
+            bad = 1
+        else:
+            fingerprint = args[0]
+    if keywords.has_key("BADARMOR"):
+        reject("ascii armour of signature was corrupt in %s." % (filename))
+        bad = 1
+    if keywords.has_key("NODATA"):
+        utils.warn("no signature found for %s." % (filename))
+        return "NOSIG"
+        #reject("no signature found in %s." % (filename))
+        #bad = 1
+
+    if bad:
+        return None
+
+    # Next check gpgv exited with a zero return code
+    if exit_status and not keywords.has_key("NO_PUBKEY"):
+        reject("gpgv failed while checking %s." % (filename))
+        if status.strip():
+            reject(utils.prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
+        else:
+            reject(utils.prefix_multi_line_string(output, " [GPG output:] "), "")
+        return None
+
+    # Sanity check the good stuff we expect
+    if not keywords.has_key("VALIDSIG"):
+        if not keywords.has_key("NO_PUBKEY"):
+            reject("signature on %s does not appear to be valid [No VALIDSIG]." % (filename))
+            bad = 1
+    else:
+        args = keywords["VALIDSIG"]
+        if len(args) < 1:
+            reject("internal error while checking signature on %s." % (filename))
+            bad = 1
+        else:
+            fingerprint = args[0]
+    if not keywords.has_key("GOODSIG") and not keywords.has_key("NO_PUBKEY"):
+        reject("signature on %s does not appear to be valid [No GOODSIG]." % (filename))
+        bad = 1
+    if not keywords.has_key("SIG_ID") and not keywords.has_key("NO_PUBKEY"):
+        reject("signature on %s does not appear to be valid [No SIG_ID]." % (filename))
+        bad = 1
+
+    # Finally ensure there's not something we don't recognise
+    known_keywords = utils.Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
+                                SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
+                                NODATA="")
+
+    for keyword in keywords.keys():
+        if not known_keywords.has_key(keyword):
+            reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], filename))
+            bad = 1
+
+    if bad:
+        return None
+    else:
+        return fingerprint
+
+################################################################################
+
+# Prepares a filename or directory (s) to be file.filename by stripping any part of the location (sub) from it.
+def poolify (s, sub):
+    for i in xrange(len(sub)):
+        if sub[i:] == s[0:len(sub)-i]:
+            return s[len(sub)-i:]
+    return s
+
+def update_archives ():
+    projectB.query("DELETE FROM archive")
+    for archive in Cnf.SubTree("Archive").List():
+        SubSec = Cnf.SubTree("Archive::%s" % (archive))
+        projectB.query("INSERT INTO archive (name, origin_server, description) VALUES ('%s', '%s', '%s')"
+                       % (archive, SubSec["OriginServer"], SubSec["Description"]))
+
+def update_components ():
+    projectB.query("DELETE FROM component")
+    for component in Cnf.SubTree("Component").List():
+        SubSec = Cnf.SubTree("Component::%s" % (component))
+        projectB.query("INSERT INTO component (name, description, meets_dfsg) VALUES ('%s', '%s', '%s')" %
+                       (component, SubSec["Description"], SubSec["MeetsDFSG"]))
+
+def update_locations ():
+    projectB.query("DELETE FROM location")
+    for location in Cnf.SubTree("Location").List():
+        SubSec = Cnf.SubTree("Location::%s" % (location))
+        archive_id = database.get_archive_id(SubSec["archive"])
+        type = SubSec.Find("type")
+        if type == "legacy-mixed":
+            projectB.query("INSERT INTO location (path, archive, type) VALUES ('%s', %d, '%s')" % (location, archive_id, SubSec["type"]))
+        else:
+            for component in Cnf.SubTree("Component").List():
+                component_id = database.get_component_id(component)
+                projectB.query("INSERT INTO location (path, component, archive, type) VALUES ('%s', %d, %d, '%s')" %
+                               (location, component_id, archive_id, SubSec["type"]))
+
+def update_architectures ():
+    projectB.query("DELETE FROM architecture")
+    for arch in Cnf.SubTree("Architectures").List():
+        projectB.query("INSERT INTO architecture (arch_string, description) VALUES ('%s', '%s')" % (arch, Cnf["Architectures::%s" % (arch)]))
+
+def update_suites ():
+    projectB.query("DELETE FROM suite")
+    for suite in Cnf.SubTree("Suite").List():
+        SubSec = Cnf.SubTree("Suite::%s" %(suite))
+        projectB.query("INSERT INTO suite (suite_name) VALUES ('%s')" % suite.lower())
+        for i in ("Version", "Origin", "Description"):
+            if SubSec.has_key(i):
+                projectB.query("UPDATE suite SET %s = '%s' WHERE suite_name = '%s'" % (i.lower(), SubSec[i], suite.lower()))
+        for architecture in Cnf.ValueList("Suite::%s::Architectures" % (suite)):
+            architecture_id = database.get_architecture_id (architecture)
+            projectB.query("INSERT INTO suite_architectures (suite, architecture) VALUES (currval('suite_id_seq'), %d)" % (architecture_id))
+
+def update_override_type():
+    projectB.query("DELETE FROM override_type")
+    for type in Cnf.ValueList("OverrideType"):
+        projectB.query("INSERT INTO override_type (type) VALUES ('%s')" % (type))
+
+def update_priority():
+    projectB.query("DELETE FROM priority")
+    for priority in Cnf.SubTree("Priority").List():
+        projectB.query("INSERT INTO priority (priority, level) VALUES ('%s', %s)" % (priority, Cnf["Priority::%s" % (priority)]))
+
+def update_section():
+    projectB.query("DELETE FROM section")
+    for component in Cnf.SubTree("Component").List():
+        if Cnf["Control-Overrides::ComponentPosition"] == "prefix":
+            suffix = ""
+            if component != 'main':
+                prefix = component + '/'
+            else:
+                prefix = ""
+        else:
+            prefix = ""
+            if component != 'main':
+                suffix = '/' + component
+            else:
+                suffix = ""
+        for section in Cnf.ValueList("Section"):
+            projectB.query("INSERT INTO section (section) VALUES ('%s%s%s')" % (prefix, section, suffix))
+
+def get_location_path(directory):
+    global location_path_cache
+
+    if location_path_cache.has_key(directory):
+        return location_path_cache[directory]
+
+    q = projectB.query("SELECT DISTINCT path FROM location WHERE path ~ '%s'" % (directory))
+    try:
+        path = q.getresult()[0][0]
+    except:
+        utils.fubar("[import-archive] get_location_path(): Couldn't get path for %s" % (directory))
+    location_path_cache[directory] = path
+    return path
+
+################################################################################
+
+def get_or_set_files_id (filename, size, md5sum, location_id):
+    global files_id_cache, files_id_serial, files_query_cache
+
+    cache_key = "_".join((filename, size, md5sum, repr(location_id)))
+    if not files_id_cache.has_key(cache_key):
+        files_id_serial += 1
+        files_query_cache.write("%d\t%s\t%s\t%s\t%d\t\\N\n" % (files_id_serial, filename, size, md5sum, location_id))
+        files_id_cache[cache_key] = files_id_serial
+
+    return files_id_cache[cache_key]
+
+###############################################################################
+
+def process_sources (filename, suite, component, archive):
+    global source_cache, source_query_cache, src_associations_query_cache, dsc_files_query_cache, source_id_serial, src_associations_id_serial, dsc_files_id_serial, source_cache_for_binaries, orig_tar_gz_cache, reject_message
+
+    suite = suite.lower()
+    suite_id = database.get_suite_id(suite)
+    try:
+        file = utils.open_file (filename)
+    except CantOpenError:
+        utils.warn("can't open '%s'" % (filename))
+        return
+    Scanner = apt_pkg.ParseTagFile(file)
+    while Scanner.Step() != 0:
+        package = Scanner.Section["package"]
+        version = Scanner.Section["version"]
+        directory = Scanner.Section["directory"]
+        dsc_file = os.path.join(Cnf["Dir::Root"], directory, "%s_%s.dsc" % (package, utils.re_no_epoch.sub('', version)))
+        # Sometimes the Directory path is a lie; check in the pool
+        if not os.path.exists(dsc_file):
+            if directory.split('/')[0] == "dists":
+                directory = Cnf["Dir::PoolRoot"] + utils.poolify(package, component)
+                dsc_file = os.path.join(Cnf["Dir::Root"], directory, "%s_%s.dsc" % (package, utils.re_no_epoch.sub('', version)))
+        if not os.path.exists(dsc_file):
+            utils.fubar("%s not found." % (dsc_file))
+        install_date = time.strftime("%Y-%m-%d", time.localtime(os.path.getmtime(dsc_file)))
+        fingerprint = check_signature(dsc_file)
+        fingerprint_id = database.get_or_set_fingerprint_id(fingerprint)
+        if reject_message:
+            utils.fubar("%s: %s" % (dsc_file, reject_message))
+        maintainer = Scanner.Section["maintainer"]
+        maintainer = maintainer.replace("'", "\\'")
+        maintainer_id = database.get_or_set_maintainer_id(maintainer)
+        location = get_location_path(directory.split('/')[0])
+        location_id = database.get_location_id (location, component, archive)
+        if not directory.endswith("/"):
+            directory += '/'
+        directory = poolify (directory, location)
+        if directory != "" and not directory.endswith("/"):
+            directory += '/'
+        no_epoch_version = utils.re_no_epoch.sub('', version)
+        # Add all files referenced by the .dsc to the files table
+        ids = []
+        for line in Scanner.Section["files"].split('\n'):
+            id = None
+            (md5sum, size, filename) = line.strip().split()
+            # Don't duplicate .orig.tar.gz's
+            if filename.endswith(".orig.tar.gz"):
+                cache_key = "%s_%s_%s" % (filename, size, md5sum)
+                if orig_tar_gz_cache.has_key(cache_key):
+                    id = orig_tar_gz_cache[cache_key]
+                else:
+                    id = get_or_set_files_id (directory + filename, size, md5sum, location_id)
+                    orig_tar_gz_cache[cache_key] = id
+            else:
+                id = get_or_set_files_id (directory + filename, size, md5sum, location_id)
+            ids.append(id)
+            # If this is the .dsc itself; save the ID for later.
+            if filename.endswith(".dsc"):
+                files_id = id
+        filename = directory + package + '_' + no_epoch_version + '.dsc'
+        cache_key = "%s_%s" % (package, version)
+        if not source_cache.has_key(cache_key):
+            nasty_key = "%s_%s" % (package, version)
+            source_id_serial += 1
+            if not source_cache_for_binaries.has_key(nasty_key):
+                source_cache_for_binaries[nasty_key] = source_id_serial
+            tmp_source_id = source_id_serial
+            source_cache[cache_key] = source_id_serial
+            source_query_cache.write("%d\t%s\t%s\t%d\t%d\t%s\t%s\n" % (source_id_serial, package, version, maintainer_id, files_id, install_date, fingerprint_id))
+            for id in ids:
+                dsc_files_id_serial += 1
+                dsc_files_query_cache.write("%d\t%d\t%d\n" % (dsc_files_id_serial, tmp_source_id,id))
+        else:
+            tmp_source_id = source_cache[cache_key]
+
+        src_associations_id_serial += 1
+        src_associations_query_cache.write("%d\t%d\t%d\n" % (src_associations_id_serial, suite_id, tmp_source_id))
+
+    file.close()
+
+###############################################################################
+
+def process_packages (filename, suite, component, archive):
+    global arch_all_cache, binary_cache, binaries_id_serial, binaries_query_cache, bin_associations_id_serial, bin_associations_query_cache, reject_message
+
+    count_total = 0
+    count_bad = 0
+    suite = suite.lower()
+    suite_id = database.get_suite_id(suite)
+    try:
+        file = utils.open_file (filename)
+    except CantOpenError:
+        utils.warn("can't open '%s'" % (filename))
+        return
+    Scanner = apt_pkg.ParseTagFile(file)
+    while Scanner.Step() != 0:
+        package = Scanner.Section["package"]
+        version = Scanner.Section["version"]
+        maintainer = Scanner.Section["maintainer"]
+        maintainer = maintainer.replace("'", "\\'")
+        maintainer_id = database.get_or_set_maintainer_id(maintainer)
+        architecture = Scanner.Section["architecture"]
+        architecture_id = database.get_architecture_id (architecture)
+        fingerprint = "NOSIG"
+        fingerprint_id = database.get_or_set_fingerprint_id(fingerprint)
+        if not Scanner.Section.has_key("source"):
+            source = package
+        else:
+            source = Scanner.Section["source"]
+        source_version = ""
+        if source.find("(") != -1:
+            m = utils.re_extract_src_version.match(source)
+            source = m.group(1)
+            source_version = m.group(2)
+        if not source_version:
+            source_version = version
+        filename = Scanner.Section["filename"]
+        if filename.endswith(".deb"):
+            type = "deb"
+        else:
+            type = "udeb"
+        location = get_location_path(filename.split('/')[0])
+        location_id = database.get_location_id (location, component.replace("/debian-installer", ""), archive)
+        filename = poolify (filename, location)
+        if architecture == "all":
+            filename = re_arch_from_filename.sub("binary-all", filename)
+        cache_key = "%s_%s" % (source, source_version)
+        source_id = source_cache_for_binaries.get(cache_key, None)
+        size = Scanner.Section["size"]
+        md5sum = Scanner.Section["md5sum"]
+        files_id = get_or_set_files_id (filename, size, md5sum, location_id)
+        cache_key = "%s_%s_%s_%d_%d_%d_%d" % (package, version, repr(source_id), architecture_id, location_id, files_id, suite_id)
+        if not arch_all_cache.has_key(cache_key):
+            arch_all_cache[cache_key] = 1
+            cache_key = "%s_%s_%s_%d" % (package, version, repr(source_id), architecture_id)
+            if not binary_cache.has_key(cache_key):
+                if not source_id:
+                    source_id = "\N"
+                    count_bad += 1
+                else:
+                    source_id = repr(source_id)
+                binaries_id_serial += 1
+                binaries_query_cache.write("%d\t%s\t%s\t%d\t%s\t%d\t%d\t%s\t%s\n" % (binaries_id_serial, package, version, maintainer_id, source_id, architecture_id, files_id, type, fingerprint_id))
+                binary_cache[cache_key] = binaries_id_serial
+                tmp_binaries_id = binaries_id_serial
+            else:
+                tmp_binaries_id = binary_cache[cache_key]
+
+            bin_associations_id_serial += 1
+            bin_associations_query_cache.write("%d\t%d\t%d\n" % (bin_associations_id_serial, suite_id, tmp_binaries_id))
+            count_total += 1
+
+    file.close()
+    if count_bad != 0:
+        print "%d binary packages processed; %d with no source match which is %.2f%%" % (count_total, count_bad, (float(count_bad)/count_total)*100)
+    else:
+        print "%d binary packages processed; 0 with no source match which is 0%%" % (count_total)
+
+###############################################################################
+
+def do_sources(sources, suite, component, server):
+    temp_filename = utils.temp_filename()
+    (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (sources, temp_filename))
+    if (result != 0):
+        utils.fubar("Gunzip invocation failed!\n%s" % (output), result)
+    print 'Processing '+sources+'...'
+    process_sources (temp_filename, suite, component, server)
+    os.unlink(temp_filename)
+
+###############################################################################
+
+def do_da_do_da ():
+    global Cnf, projectB, query_cache, files_query_cache, source_query_cache, src_associations_query_cache, dsc_files_query_cache, bin_associations_query_cache, binaries_query_cache
+
+    Cnf = utils.get_conf()
+    Arguments = [('a', "action", "Import-Archive::Options::Action"),
+                 ('h', "help", "Import-Archive::Options::Help")]
+    for i in [ "action", "help" ]:
+        if not Cnf.has_key("Import-Archive::Options::%s" % (i)):
+            Cnf["Import-Archive::Options::%s" % (i)] = ""
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Import-Archive::Options")
+    if Options["Help"]:
+        usage()
+
+    if not Options["Action"]:
+        utils.warn("""no -a/--action given; not doing anything.
+Please read the documentation before running this script.
+""")
+        usage(1)
+
+    print "Re-Creating DB..."
+    (result, output) = commands.getstatusoutput("psql -f init_pool.sql template1")
+    if (result != 0):
+        utils.fubar("psql invocation failed!\n", result)
+    print output
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+
+    database.init (Cnf, projectB)
+
+    print "Adding static tables from conf file..."
+    projectB.query("BEGIN WORK")
+    update_architectures()
+    update_components()
+    update_archives()
+    update_locations()
+    update_suites()
+    update_override_type()
+    update_priority()
+    update_section()
+    projectB.query("COMMIT WORK")
+
+    files_query_cache = utils.open_file(Cnf["Import-Archive::ExportDir"]+"files","w")
+    source_query_cache = utils.open_file(Cnf["Import-Archive::ExportDir"]+"source","w")
+    src_associations_query_cache = utils.open_file(Cnf["Import-Archive::ExportDir"]+"src_associations","w")
+    dsc_files_query_cache = utils.open_file(Cnf["Import-Archive::ExportDir"]+"dsc_files","w")
+    binaries_query_cache = utils.open_file(Cnf["Import-Archive::ExportDir"]+"binaries","w")
+    bin_associations_query_cache = utils.open_file(Cnf["Import-Archive::ExportDir"]+"bin_associations","w")
+
+    projectB.query("BEGIN WORK")
+    # Process Sources files to popoulate `source' and friends
+    for location in Cnf.SubTree("Location").List():
+        SubSec = Cnf.SubTree("Location::%s" % (location))
+        server = SubSec["Archive"]
+        type = Cnf.Find("Location::%s::Type" % (location))
+        if type == "legacy-mixed":
+            sources = location + 'Sources.gz'
+            suite = Cnf.Find("Location::%s::Suite" % (location))
+            do_sources(sources, suite, "",  server)
+        elif type == "legacy" or type == "pool":
+            for suite in Cnf.ValueList("Location::%s::Suites" % (location)):
+                for component in Cnf.SubTree("Component").List():
+                    sources = Cnf["Dir::Root"] + "dists/" + Cnf["Suite::%s::CodeName" % (suite)] + '/' + component + '/source/' + 'Sources.gz'
+                    do_sources(sources, suite, component, server)
+        else:
+            utils.fubar("Unknown location type ('%s')." % (type))
+
+    # Process Packages files to populate `binaries' and friends
+
+    for location in Cnf.SubTree("Location").List():
+        SubSec = Cnf.SubTree("Location::%s" % (location))
+        server = SubSec["Archive"]
+        type = Cnf.Find("Location::%s::Type" % (location))
+        if type == "legacy-mixed":
+            packages = location + 'Packages'
+            suite = Cnf.Find("Location::%s::Suite" % (location))
+            print 'Processing '+location+'...'
+            process_packages (packages, suite, "", server)
+        elif type == "legacy" or type == "pool":
+            for suite in Cnf.ValueList("Location::%s::Suites" % (location)):
+                udeb_components = map(lambda x: x+"/debian-installer",
+                                      Cnf.ValueList("Suite::%s::UdebComponents" % suite))
+                for component in Cnf.SubTree("Component").List() + udeb_components:
+                    architectures = filter(utils.real_arch,
+                                           Cnf.ValueList("Suite::%s::Architectures" % (suite)))
+                    for architecture in architectures:
+                        packages = Cnf["Dir::Root"] + "dists/" + Cnf["Suite::%s::CodeName" % (suite)] + '/' + component + '/binary-' + architecture + '/Packages'
+                        print 'Processing '+packages+'...'
+                        process_packages (packages, suite, component, server)
+
+    files_query_cache.close()
+    source_query_cache.close()
+    src_associations_query_cache.close()
+    dsc_files_query_cache.close()
+    binaries_query_cache.close()
+    bin_associations_query_cache.close()
+    print "Writing data to `files' table..."
+    projectB.query("COPY files FROM '%s'" % (Cnf["Import-Archive::ExportDir"]+"files"))
+    print "Writing data to `source' table..."
+    projectB.query("COPY source FROM '%s'" % (Cnf["Import-Archive::ExportDir"]+"source"))
+    print "Writing data to `src_associations' table..."
+    projectB.query("COPY src_associations FROM '%s'" % (Cnf["Import-Archive::ExportDir"]+"src_associations"))
+    print "Writing data to `dsc_files' table..."
+    projectB.query("COPY dsc_files FROM '%s'" % (Cnf["Import-Archive::ExportDir"]+"dsc_files"))
+    print "Writing data to `binaries' table..."
+    projectB.query("COPY binaries FROM '%s'" % (Cnf["Import-Archive::ExportDir"]+"binaries"))
+    print "Writing data to `bin_associations' table..."
+    projectB.query("COPY bin_associations FROM '%s'" % (Cnf["Import-Archive::ExportDir"]+"bin_associations"))
+    print "Committing..."
+    projectB.query("COMMIT WORK")
+
+    # Add the constraints and otherwise generally clean up the database.
+    # See add_constraints.sql for more details...
+
+    print "Running add_constraints.sql..."
+    (result, output) = commands.getstatusoutput("psql %s < add_constraints.sql" % (Cnf["DB::Name"]))
+    print output
+    if (result != 0):
+        utils.fubar("psql invocation failed!\n%s" % (output), result)
+
+    return
+
+################################################################################
+
+def main():
+    utils.try_with_debug(do_da_do_da)
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/import_keyring.py b/dak/import_keyring.py
new file mode 100755 (executable)
index 0000000..a70a2e7
--- /dev/null
@@ -0,0 +1,313 @@
+#!/usr/bin/env python
+
+# Imports a keyring into the database
+# Copyright (C) 2007  Anthony Towns <aj@erisian.com.au>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+from daklib import database
+from daklib import utils
+import sys, os, re
+import apt_pkg, pg, ldap, email.Utils
+
+# Globals
+Cnf = None
+Options = None
+projectB = None
+
+################################################################################
+
+def get_uid_info():
+    byname = {}
+    byid = {}
+    q = projectB.query("SELECT id, uid, name FROM uid")
+    for (id, uid, name) in q.getresult():
+        byname[uid] = (id, name)
+        byid[id] = (uid, name)
+    return (byname, byid)
+
+def get_fingerprint_info():
+    fins = {}
+    q = projectB.query("SELECT f.fingerprint, f.id, f.uid, f.keyring FROM fingerprint f")
+    for (fingerprint, fingerprint_id, uid, keyring) in q.getresult():
+        fins[fingerprint] = (uid, fingerprint_id, keyring)
+    return fins
+
+################################################################################
+
+def get_ldap_name(entry):
+    name = []
+    for k in ["cn", "mn", "sn"]:
+        ret = entry.get(k)
+        if ret and ret[0] != "" and ret[0] != "-":
+            name.append(ret[0])
+    return " ".join(name)
+
+################################################################################
+
+class Keyring:
+    gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
+                     " --with-colons --fingerprint --fingerprint"
+    keys = {}
+    fpr_lookup = {}
+
+    def de_escape_gpg_str(self, str):
+        esclist = re.split(r'(\\x..)', str)
+        for x in range(1,len(esclist),2):
+            esclist[x] = "%c" % (int(esclist[x][2:],16))
+        return "".join(esclist)
+
+    def __init__(self, keyring):
+        k = os.popen(self.gpg_invocation % keyring, "r")
+        keys = self.keys
+        key = None
+        fpr_lookup = self.fpr_lookup
+        signingkey = False
+        for line in k.xreadlines():
+            field = line.split(":")
+            if field[0] == "pub":
+                key = field[4]
+                (name, addr) = email.Utils.parseaddr(field[9])
+                name = re.sub(r"\s*[(].*[)]", "", name)
+                if name == "" or addr == "" or "@" not in addr:
+                    name = field[9]
+                    addr = "invalid-uid"
+                name = self.de_escape_gpg_str(name)
+                keys[key] = {"email": addr}
+                if name != "": keys[key]["name"] = name
+                keys[key]["aliases"] = [name]
+                keys[key]["fingerprints"] = []
+                signingkey = True
+            elif key and field[0] == "sub" and len(field) >= 12:
+                signingkey = ("s" in field[11])
+            elif key and field[0] == "uid":
+                (name, addr) = email.Utils.parseaddr(field[9])
+                if name and name not in keys[key]["aliases"]:
+                    keys[key]["aliases"].append(name)
+            elif signingkey and field[0] == "fpr":
+                keys[key]["fingerprints"].append(field[9])
+                fpr_lookup[field[9]] = key
+
+    def generate_desired_users(self):
+        if Options["Generate-Users"]:
+            format = Options["Generate-Users"]
+            return self.generate_users_from_keyring(format)
+        if Options["Import-Ldap-Users"]:
+            return self.import_users_from_ldap()
+        return ({}, {})
+
+    def import_users_from_ldap(self):
+        LDAPDn = Cnf["Import-LDAP-Fingerprints::LDAPDn"]
+        LDAPServer = Cnf["Import-LDAP-Fingerprints::LDAPServer"]
+        l = ldap.open(LDAPServer)
+        l.simple_bind_s("","")
+        Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
+               "(&(keyfingerprint=*)(gidnumber=%s))" % (Cnf["Import-Users-From-Passwd::ValidGID"]),
+               ["uid", "keyfingerprint", "cn", "mn", "sn"])
+
+        ldap_fin_uid_id = {}
+
+        byuid = {}
+        byname = {}
+        keys = self.keys
+        fpr_lookup = self.fpr_lookup
+
+        for i in Attrs:
+            entry = i[1]
+            uid = entry["uid"][0]
+            name = get_ldap_name(entry)
+            fingerprints = entry["keyFingerPrint"]
+            id = None
+            for f in fingerprints:
+                key = fpr_lookup.get(f, None)
+                if key not in keys: continue
+                keys[key]["uid"] = uid
+
+                if id != None: continue
+                id = database.get_or_set_uid_id(uid)
+                byuid[id] = (uid, name)
+                byname[uid] = (id, name)
+
+        return (byname, byuid)
+
+    def generate_users_from_keyring(self, format):
+        byuid = {}
+        byname = {}
+        keys = self.keys
+        any_invalid = False
+        for x in keys.keys():
+            if keys[x]["email"] == "invalid-uid":
+                any_invalid = True
+                keys[x]["uid"] = format % "invalid-uid"
+            else:
+                uid = format % keys[x]["email"]
+                id = database.get_or_set_uid_id(uid)
+                byuid[id] = (uid, keys[x]["name"])
+                byname[uid] = (id, keys[x]["name"])
+                keys[x]["uid"] = uid
+        if any_invalid:
+            uid = format % "invalid-uid"
+            id = database.get_or_set_uid_id(uid)
+            byuid[id] = (uid, "ungeneratable user id")
+            byname[uid] = (id, "ungeneratable user id")
+        return (byname, byuid)
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak import-keyring [OPTION]... [KEYRING]
+  -h, --help                  show this help and exit.
+  -L, --import-ldap-users     generate uid entries for keyring from LDAP
+  -U, --generate-users FMT    generate uid entries from keyring as FMT"""
+    sys.exit(exit_code)
+
+
+################################################################################
+
+def main():
+    global Cnf, projectB, Options
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Import-Keyring::Options::Help"),
+                 ('L',"import-ldap-users","Import-Keyring::Options::Import-Ldap-Users"),
+                 ('U',"generate-users","Import-Keyring::Options::Generate-Users", "HasArg"),
+                ]
+
+    for i in [ "help", "report-changes", "generate-users", "import-ldap-users" ]:
+        if not Cnf.has_key("Import-Keyring::Options::%s" % (i)):
+            Cnf["Import-Keyring::Options::%s" % (i)] = ""
+
+    keyring_names = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    ### Parse options
+
+    Options = Cnf.SubTree("Import-Keyring::Options")
+    if Options["Help"]:
+        usage()
+
+    if len(keyring_names) != 1:
+        usage(1)
+
+    ### Keep track of changes made
+
+    changes = []   # (uid, changes strings)
+
+    ### Initialise
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    projectB.query("BEGIN WORK")
+
+    ### Cache all the existing fingerprint entries
+
+    db_fin_info = get_fingerprint_info()
+
+    ### Parse the keyring
+
+    keyringname = keyring_names[0]
+    keyring = Keyring(keyringname)
+
+    keyring_id = database.get_or_set_keyring_id(
+                        keyringname.split("/")[-1])
+
+    ### Generate new uid entries if they're needed (from LDAP or the keyring)
+    (desuid_byname, desuid_byid) = keyring.generate_desired_users()
+
+    ### Cache all the existing uid entries
+    (db_uid_byname, db_uid_byid) = get_uid_info()
+
+    ### Update full names of applicable users
+    for id in desuid_byid.keys():
+        uid = (id, desuid_byid[id][0])
+        name = desuid_byid[id][1]
+        oname = db_uid_byid[id][1]
+        if name and oname != name:
+            changes.append((uid[1], "Full name: %s" % (name)))
+            projectB.query("UPDATE uid SET name = '%s' WHERE id = %s" %
+                (pg.escape_string(name), id))
+
+    # The fingerprint table (fpr) points to a uid and a keyring.
+    #   If the uid is being decided here (ldap/generate) we set it to it.
+    #   Otherwise, if the fingerprint table already has a uid (which we've
+    #     cached earlier), we preserve it.
+    #   Otherwise we leave it as None
+
+    fpr = {}
+    for z in keyring.keys.keys():
+        id = db_uid_byname.get(keyring.keys[z].get("uid", None), [None])[0]
+        if id == None:
+            id = db_fin_info.get(keyring.keys[z]["fingerprints"][0], [None])[0]
+        for y in keyring.keys[z]["fingerprints"]:
+            fpr[y] = (id,keyring_id)
+
+    # For any keys that used to be in this keyring, disassociate them.
+    # We don't change the uid, leaving that for historical info; if
+    # the id should change, it'll be set when importing another keyring.
+
+    for f,(u,fid,kr) in db_fin_info.iteritems():
+        if kr != keyring_id: continue
+        if f in fpr: continue
+        changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s" % (f)))
+        projectB.query("UPDATE fingerprint SET keyring = NULL WHERE id = %d" % (fid))
+
+    # For the keys in this keyring, add/update any fingerprints that've
+    # changed.
+
+    for f in fpr:
+        newuid = fpr[f][0]
+        newuiduid = db_uid_byid.get(newuid, [None])[0]
+        (olduid, oldfid, oldkid) = db_fin_info.get(f, [-1,-1,-1])
+        if olduid == None: olduid = -1
+        if oldkid == None: oldkid = -1
+        if oldfid == -1:
+            changes.append((newuiduid, "Added key: %s" % (f)))
+            if newuid:
+                projectB.query("INSERT INTO fingerprint (fingerprint, uid, keyring) VALUES ('%s', %d, %d)" % (f, newuid, keyring_id))
+            else:
+                projectB.query("INSERT INTO fingerprint (fingerprint, keyring) VALUES ('%s', %d)" % (f, keyring_id))
+        else:
+            if newuid and olduid != newuid:
+                if olduid != -1:
+                    changes.append((newuiduid, "Linked key: %s" % f))
+                    changes.append((newuiduid, "  (formerly belonging to %s)" % (db_uid_byid[olduid][0])))
+                else:
+                    changes.append((newuiduid, "Linked key: %s" % f))
+                    changes.append((newuiduid, "  (formerly unowned)"))
+                projectB.query("UPDATE fingerprint SET uid = %d WHERE id = %d" % (newuid, oldfid))
+
+            if oldkid != keyring_id:
+                projectB.query("UPDATE fingerprint SET keyring = %d WHERE id = %d" % (keyring_id, oldfid))
+
+    # All done!
+
+    projectB.query("COMMIT WORK")
+
+    changesd = {}
+    for (k, v) in changes:
+        if k not in changesd: changesd[k] = ""
+        changesd[k] += "    %s\n" % (v)
+
+    keys = changesd.keys()
+    keys.sort()
+    for k in keys:
+        print "%s\n%s\n" % (k, changesd[k])
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/import_ldap_fingerprints.py b/dak/import_ldap_fingerprints.py
new file mode 100755 (executable)
index 0000000..b568285
--- /dev/null
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+
+# Sync fingerprint and uid tables with a debian.org LDAP DB
+# Copyright (C) 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <elmo>  ping@debian.org ?
+# <aj>    missing@ ? wtfru@ ?
+# <elmo>  giggle
+# <elmo>  I like wtfru
+# <aj>    all you have to do is retrofit wtfru into an acronym and no one
+#         could possibly be offended!
+# <elmo>  aj: worried terriers for russian unity ?
+# <aj>    uhhh
+# <aj>    ooookkkaaaaay
+# <elmo>  wthru is a little less offensive maybe?  but myabe that's
+#         just because I read h as heck, not hell
+# <elmo>  ho hum
+# <aj>    (surely the "f" stands for "freedom" though...)
+# <elmo>  where the freedom are you?
+# <aj>    'xactly
+# <elmo>  or worried terriers freed (of) russian unilateralism ?
+# <aj>    freedom -- it's the "foo" of the 21st century
+# <aj>    oo, how about "wat@" as in wherefore art thou?
+# <neuro> or worried attack terriers
+# <aj>    Waning Trysts Feared - Return? Unavailable?
+# <aj>    (i find all these terriers more worrying, than worried)
+# <neuro> worrying attack terriers, then
+
+################################################################################
+
+import commands, ldap, pg, re, sys
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+re_gpg_fingerprint = re.compile(r"^\s+Key fingerprint = (.*)$", re.MULTILINE)
+re_debian_address = re.compile(r"^.*<(.*)@debian\.org>$", re.MULTILINE)
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak import-ldap-fingerprints
+Syncs fingerprint and uid tables with a debian.org LDAP DB
+
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def get_ldap_value(entry, value):
+    ret = entry.get(value)
+    if not ret or ret[0] == "" or ret[0] == "-":
+        return ""
+    else:
+        # FIXME: what about > 0 ?
+        return ret[0] + " "
+
+def get_ldap_name(entry):
+    name = get_ldap_value(entry, "cn")
+    name += get_ldap_value(entry, "mn")
+    name += get_ldap_value(entry, "sn")
+    return name.rstrip()
+
+def escape_string(str):
+    return str.replace("'", "\\'")
+
+def main():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Import-LDAP-Fingerprints::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Import-LDAP-Fingerprints::Options::%s" % (i)):
+            Cnf["Import-LDAP-Fingerprints::Options::%s" % (i)] = ""
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Import-LDAP-Fingerprints::Options")
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    LDAPDn = Cnf["Import-LDAP-Fingerprints::LDAPDn"]
+    LDAPServer = Cnf["Import-LDAP-Fingerprints::LDAPServer"]
+    l = ldap.open(LDAPServer)
+    l.simple_bind_s("","")
+    Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
+                       "(&(keyfingerprint=*)(gidnumber=%s))" % (Cnf["Import-Users-From-Passwd::ValidGID"]),
+                       ["uid", "keyfingerprint", "cn", "mn", "sn"])
+
+
+    projectB.query("BEGIN WORK")
+
+
+    # Sync LDAP with DB
+    db_fin_uid = {}
+    db_uid_name = {}
+    ldap_fin_uid_id = {}
+    q = projectB.query("""
+SELECT f.fingerprint, f.id, u.uid FROM fingerprint f, uid u WHERE f.uid = u.id
+ UNION SELECT f.fingerprint, f.id, null FROM fingerprint f where f.uid is null""")
+    for i in q.getresult():
+        (fingerprint, fingerprint_id, uid) = i
+        db_fin_uid[fingerprint] = (uid, fingerprint_id)
+
+    q = projectB.query("SELECT id, name FROM uid")
+    for i in q.getresult():
+        (uid, name) = i
+        db_uid_name[uid] = name
+
+    for i in Attrs:
+        entry = i[1]
+        fingerprints = entry["keyFingerPrint"]
+        uid = entry["uid"][0]
+        name = get_ldap_name(entry)
+        uid_id = database.get_or_set_uid_id(uid)
+
+        if not db_uid_name.has_key(uid_id) or db_uid_name[uid_id] != name:
+            q = projectB.query("UPDATE uid SET name = '%s' WHERE id = %d" % (escape_string(name), uid_id))
+            print "Assigning name of %s as %s" % (uid, name)
+
+        for fingerprint in fingerprints:
+            ldap_fin_uid_id[fingerprint] = (uid, uid_id)
+            if db_fin_uid.has_key(fingerprint):
+                (existing_uid, fingerprint_id) = db_fin_uid[fingerprint]
+                if not existing_uid:
+                    q = projectB.query("UPDATE fingerprint SET uid = %s WHERE id = %s" % (uid_id, fingerprint_id))
+                    print "Assigning %s to 0x%s." % (uid, fingerprint)
+                elif existing_uid == uid:
+                    pass
+                elif existing_uid[:3] == "dm:":
+                    q = projectB.query("UPDATE fingerprint SET uid = %s WHERE id = %s" % (uid_id, fingerprint_id))
+                    print "Promoting DM %s to DD %s with keyid 0x%s." % (existing_uid, uid, fingerprint)
+                else:
+                    utils.warn("%s has %s in LDAP, but projectB says it should be %s." % (uid, fingerprint, existing_uid))
+
+    # Try to update people who sign with non-primary key
+    q = projectB.query("SELECT fingerprint, id FROM fingerprint WHERE uid is null")
+    for i in q.getresult():
+        (fingerprint, fingerprint_id) = i
+        cmd = "gpg --no-default-keyring %s --fingerprint %s" \
+              % (utils.gpg_keyring_args(), fingerprint)
+        (result, output) = commands.getstatusoutput(cmd)
+        if result == 0:
+            m = re_gpg_fingerprint.search(output)
+            if not m:
+                print output
+                utils.fubar("0x%s: No fingerprint found in gpg output but it returned 0?\n%s" % (fingerprint, utils.prefix_multi_line_string(output, " [GPG output:] ")))
+            primary_key = m.group(1)
+            primary_key = primary_key.replace(" ","")
+            if not ldap_fin_uid_id.has_key(primary_key):
+                utils.warn("0x%s (from 0x%s): no UID found in LDAP" % (primary_key, fingerprint))
+            else:
+                (uid, uid_id) = ldap_fin_uid_id[primary_key]
+                q = projectB.query("UPDATE fingerprint SET uid = %s WHERE id = %s" % (uid_id, fingerprint_id))
+                print "Assigning %s to 0x%s." % (uid, fingerprint)
+        else:
+            extra_keyrings = ""
+            for keyring in Cnf.ValueList("Import-LDAP-Fingerprints::ExtraKeyrings"):
+                extra_keyrings += " --keyring=%s" % (keyring)
+            cmd = "gpg %s %s --list-key %s" \
+                  % (utils.gpg_keyring_args(), extra_keyrings, fingerprint)
+            (result, output) = commands.getstatusoutput(cmd)
+            if result != 0:
+                cmd = "gpg --keyserver=%s --allow-non-selfsigned-uid --recv-key %s" % (Cnf["Import-LDAP-Fingerprints::KeyServer"], fingerprint)
+                (result, output) = commands.getstatusoutput(cmd)
+                if result != 0:
+                    print "0x%s: NOT found on keyserver." % (fingerprint)
+                    print cmd
+                    print result
+                    print output
+                    continue
+                else:
+                    cmd = "gpg --list-key %s" % (fingerprint)
+                    (result, output) = commands.getstatusoutput(cmd)
+                    if result != 0:
+                        print "0x%s: --list-key returned error after --recv-key didn't." % (fingerprint)
+                        print cmd
+                        print result
+                        print output
+                        continue
+            m = re_debian_address.search(output)
+            if m:
+                guess_uid = m.group(1)
+            else:
+                guess_uid = "???"
+            name = " ".join(output.split('\n')[0].split()[3:])
+            print "0x%s -> %s -> %s" % (fingerprint, name, guess_uid)
+
+            # FIXME: make me optionally non-interactive
+            # FIXME: default to the guessed ID
+            uid = None
+            while not uid:
+                uid = utils.our_raw_input("Map to which UID ? ")
+                Attrs = l.search_s(LDAPDn,ldap.SCOPE_ONELEVEL,"(uid=%s)" % (uid), ["cn","mn","sn"])
+                if not Attrs:
+                    print "That UID doesn't exist in LDAP!"
+                    uid = None
+                else:
+                    entry = Attrs[0][1]
+                    name = get_ldap_name(entry)
+                    prompt = "Map to %s - %s (y/N) ? " % (uid, name.replace("  "," "))
+                    yn = utils.our_raw_input(prompt).lower()
+                    if yn == "y":
+                        uid_id = database.get_or_set_uid_id(uid)
+                        projectB.query("UPDATE fingerprint SET uid = %s WHERE id = %s" % (uid_id, fingerprint_id))
+                        print "Assigning %s to 0x%s." % (uid, fingerprint)
+                    else:
+                        uid = None
+    projectB.query("COMMIT WORK")
+
+############################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/import_users_from_passwd.py b/dak/import_users_from_passwd.py
new file mode 100755 (executable)
index 0000000..b182f60
--- /dev/null
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+
+# Sync PostgreSQL users with system users
+# Copyright (C) 2001, 2002, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <aj> ARRRGGGHHH
+# <aj> what's wrong with me!?!?!?
+# <aj> i was just nice to some mormon doorknockers!!!
+# <Omnic> AJ?!?!
+# <aj> i know!!!!!
+# <Omnic> I'm gonna have to kick your ass when you come over
+# <Culus> aj: GET THE HELL OUT OF THE CABAL! :P
+
+################################################################################
+
+import pg, pwd, sys
+import apt_pkg
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak import-users-from-passwd [OPTION]...
+Sync PostgreSQL's users with system users.
+
+  -h, --help                 show this help and exit
+  -n, --no-action            don't do anything
+  -q, --quiet                be quiet about what is being done
+  -v, --verbose              explain what is being done"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def main ():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('n', "no-action", "Import-Users-From-Passwd::Options::No-Action"),
+                 ('q', "quiet", "Import-Users-From-Passwd::Options::Quiet"),
+                 ('v', "verbose", "Import-Users-From-Passwd::Options::Verbose"),
+                 ('h', "help", "Import-Users-From-Passwd::Options::Help")]
+    for i in [ "no-action", "quiet", "verbose", "help" ]:
+        if not Cnf.has_key("Import-Users-From-Passwd::Options::%s" % (i)):
+            Cnf["Import-Users-From-Passwd::Options::%s" % (i)] = ""
+
+    arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Import-Users-From-Passwd::Options")
+
+    if Options["Help"]:
+        usage()
+    elif arguments:
+        utils.warn("dak import-users-from-passwd takes no non-option arguments.")
+        usage(1)
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    valid_gid = int(Cnf.get("Import-Users-From-Passwd::ValidGID",""))
+
+    passwd_unames = {}
+    for entry in pwd.getpwall():
+        uname = entry[0]
+        gid = entry[3]
+        if valid_gid and gid != valid_gid:
+            if Options["Verbose"]:
+                print "Skipping %s (GID %s != Valid GID %s)." % (uname, gid, valid_gid)
+            continue
+        passwd_unames[uname] = ""
+
+    postgres_unames = {}
+    q = projectB.query("SELECT usename FROM pg_user")
+    ql = q.getresult()
+    for i in ql:
+        uname = i[0]
+        postgres_unames[uname] = ""
+
+    known_postgres_unames = {}
+    for i in Cnf.get("Import-Users-From-Passwd::KnownPostgres","").split(","):
+        uname = i.strip()
+        known_postgres_unames[uname] = ""
+
+    keys = postgres_unames.keys()
+    keys.sort()
+    for uname in keys:
+        if not passwd_unames.has_key(uname)and not known_postgres_unames.has_key(uname):
+            print "W: %s is in Postgres but not the passwd file or list of known Postgres users." % (uname)
+
+    keys = passwd_unames.keys()
+    keys.sort()
+    for uname in keys:
+        if not postgres_unames.has_key(uname):
+            if not Options["Quiet"]:
+                print "Creating %s user in Postgres." % (uname)
+            if not Options["No-Action"]:
+                q = projectB.query('CREATE USER "%s"' % (uname))
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/init_db.py b/dak/init_db.py
new file mode 100755 (executable)
index 0000000..d40ad0c
--- /dev/null
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+
+"""Sync dak.conf configuartion file and the SQL database"""
+# Copyright (C) 2000, 2001, 2002, 2003, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import pg, sys
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+################################################################################
+
+def usage(exit_code=0):
+    """Print a usage message and exit with 'exit_code'."""
+
+    print """Usage: dak init-db
+Initalizes some tables in the projectB database based on the config file.
+
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def sql_get (config, key):
+    """Return the value of config[key] in quotes or NULL if it doesn't exist."""
+
+    if config.has_key(key):
+        return "'%s'" % (config[key])
+    else:
+        return "NULL"
+
+################################################################################
+
+def do_archive():
+    """Initalize the archive table."""
+
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM archive")
+    for name in Cnf.SubTree("Archive").List():
+        archive_config = Cnf.SubTree("Archive::%s" % (name))
+        origin_server = sql_get(archive_config, "OriginServer")
+        description = sql_get(archive_config, "Description")
+        projectB.query("INSERT INTO archive (name, origin_server, description) "
+                       "VALUES ('%s', %s, %s)"
+                       % (name, origin_server, description))
+    projectB.query("COMMIT WORK")
+
+def do_architecture():
+    """Initalize the architecture table."""
+
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM architecture")
+    for arch in Cnf.SubTree("Architectures").List():
+        description = Cnf["Architectures::%s" % (arch)]
+        projectB.query("INSERT INTO architecture (arch_string, description) "
+                       "VALUES ('%s', '%s')" % (arch, description))
+    projectB.query("COMMIT WORK")
+
+def do_component():
+    """Initalize the component table."""
+
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM component")
+    for name in Cnf.SubTree("Component").List():
+        component_config = Cnf.SubTree("Component::%s" % (name))
+        description = sql_get(component_config, "Description")
+        if component_config.get("MeetsDFSG").lower() == "true":
+            meets_dfsg = "true"
+        else:
+            meets_dfsg = "false"
+        projectB.query("INSERT INTO component (name, description, meets_dfsg) "
+                       "VALUES ('%s', %s, %s)"
+                       % (name, description, meets_dfsg))
+    projectB.query("COMMIT WORK")
+
+def do_location():
+    """Initalize the location table."""
+
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM location")
+    for location in Cnf.SubTree("Location").List():
+        location_config = Cnf.SubTree("Location::%s" % (location))
+        archive_id = database.get_archive_id(location_config["Archive"])
+        if archive_id == -1:
+            utils.fubar("Archive '%s' for location '%s' not found."
+                               % (location_config["Archive"], location))
+        location_type = location_config.get("type")
+        if location_type == "legacy-mixed":
+            projectB.query("INSERT INTO location (path, archive, type) VALUES "
+                           "('%s', %d, '%s')"
+                           % (location, archive_id, location_config["type"]))
+        elif location_type == "legacy" or location_type == "pool":
+            for component in Cnf.SubTree("Component").List():
+                component_id = database.get_component_id(component)
+                projectB.query("INSERT INTO location (path, component, "
+                               "archive, type) VALUES ('%s', %d, %d, '%s')"
+                               % (location, component_id, archive_id,
+                                  location_type))
+        else:
+            utils.fubar("E: type '%s' not recognised in location %s."
+                               % (location_type, location))
+    projectB.query("COMMIT WORK")
+
+def do_suite():
+    """Initalize the suite table."""
+
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM suite")
+    for suite in Cnf.SubTree("Suite").List():
+        suite_config = Cnf.SubTree("Suite::%s" %(suite))
+        version = sql_get(suite_config, "Version")
+        origin = sql_get(suite_config, "Origin")
+        description = sql_get(suite_config, "Description")
+        projectB.query("INSERT INTO suite (suite_name, version, origin, "
+                       "description) VALUES ('%s', %s, %s, %s)"
+                       % (suite.lower(), version, origin, description))
+        for architecture in Cnf.ValueList("Suite::%s::Architectures" % (suite)):
+            architecture_id = database.get_architecture_id (architecture)
+            if architecture_id < 0:
+                utils.fubar("architecture '%s' not found in architecture"
+                                   " table for suite %s."
+                                   % (architecture, suite))
+            projectB.query("INSERT INTO suite_architectures (suite, "
+                           "architecture) VALUES (currval('suite_id_seq'), %d)"
+                           % (architecture_id))
+    projectB.query("COMMIT WORK")
+
+def do_override_type():
+    """Initalize the override_type table."""
+
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM override_type")
+    for override_type in Cnf.ValueList("OverrideType"):
+        projectB.query("INSERT INTO override_type (type) VALUES ('%s')"
+                       % (override_type))
+    projectB.query("COMMIT WORK")
+
+def do_priority():
+    """Initialize the priority table."""
+
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM priority")
+    for priority in Cnf.SubTree("Priority").List():
+        projectB.query("INSERT INTO priority (priority, level) VALUES "
+                       "('%s', %s)"
+                       % (priority, Cnf["Priority::%s" % (priority)]))
+    projectB.query("COMMIT WORK")
+
+def do_section():
+    """Initalize the section table."""
+    projectB.query("BEGIN WORK")
+    projectB.query("DELETE FROM section")
+    for component in Cnf.SubTree("Component").List():
+        if Cnf["Control-Overrides::ComponentPosition"] == "prefix":
+            suffix = ""
+            if component != "main":
+                prefix = component + '/'
+            else:
+                prefix = ""
+        else:
+            prefix = ""
+            if component != "main":
+                suffix = '/' + component
+            else:
+                suffix = ""
+        for section in Cnf.ValueList("Section"):
+            projectB.query("INSERT INTO section (section) VALUES "
+                           "('%s%s%s')" % (prefix, section, suffix))
+    projectB.query("COMMIT WORK")
+
+################################################################################
+
+def main ():
+    """Sync dak.conf configuartion file and the SQL database"""
+
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+    arguments = [('h', "help", "Init-DB::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Init-DB::Options::%s" % (i)):
+            Cnf["Init-DB::Options::%s" % (i)] = ""
+
+    arguments = apt_pkg.ParseCommandLine(Cnf, arguments, sys.argv)
+
+    options = Cnf.SubTree("Init-DB::Options")
+    if options["Help"]:
+        usage()
+    elif arguments:
+        utils.warn("dak init-db takes no arguments.")
+        usage(exit_code=1)
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"],
+                          int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    do_archive()
+    do_architecture()
+    do_component()
+    do_location()
+    do_suite()
+    do_override_type()
+    do_priority()
+    do_section()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/init_dirs.py b/dak/init_dirs.py
new file mode 100755 (executable)
index 0000000..d095eee
--- /dev/null
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+
+"""Initial setup of an archive."""
+# Copyright (C) 2002, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, sys
+import apt_pkg
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+AptCnf = None
+
+################################################################################
+
+def usage(exit_code=0):
+    """Print a usage message and exit with 'exit_code'."""
+
+    print """Usage: dak init-dirs
+Creates directories for an archive based on dak.conf configuration file.
+
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def do_dir(target, config_name):
+    """If 'target' exists, make sure it is a directory.  If it doesn't, create
+it."""
+
+    if os.path.exists(target):
+        if not os.path.isdir(target):
+            utils.fubar("%s (%s) is not a directory."
+                               % (target, config_name))
+    else:
+        print "Creating %s ..." % (target)
+        os.makedirs(target)
+
+def process_file(config, config_name):
+    """Create directories for a config entry that's a filename."""
+
+    if config.has_key(config_name):
+        target = os.path.dirname(config[config_name])
+        do_dir(target, config_name)
+
+def process_tree(config, tree):
+    """Create directories for a config tree."""
+
+    for entry in config.SubTree(tree).List():
+        entry = entry.lower()
+        if tree == "Dir":
+            if entry in [ "poolroot", "queue" , "morguereject" ]:
+                continue
+        config_name = "%s::%s" % (tree, entry)
+        target = config[config_name]
+        do_dir(target, config_name)
+
+def process_morguesubdir(subdir):
+    """Create directories for morgue sub directories."""
+
+    config_name = "%s::MorgueSubDir" % (subdir)
+    if Cnf.has_key(config_name):
+        target = os.path.join(Cnf["Dir::Morgue"], Cnf[config_name])
+        do_dir(target, config_name)
+
+######################################################################
+
+def create_directories():
+    """Create directories referenced in dak.conf and apt.conf."""
+
+    # Process directories from dak.conf
+    process_tree(Cnf, "Dir")
+    process_tree(Cnf, "Dir::Queue")
+    for config_name in [ "Dinstall::LockFile", "Rm::LogFile",
+                         "Import-Archive::ExportDir" ]:
+        process_file(Cnf, config_name)
+    for subdir in [ "Clean-Queues", "Clean-Suites" ]:
+        process_morguesubdir(subdir)
+
+    # Process directories from apt.conf
+    process_tree(AptCnf, "Dir")
+    for tree in AptCnf.SubTree("Tree").List():
+        config_name = "Tree::%s" % (tree)
+        tree_dir = os.path.join(Cnf["Dir::Root"], tree)
+        do_dir(tree_dir, tree)
+        for filename in [ "FileList", "SourceFileList" ]:
+            process_file(AptCnf, "%s::%s" % (config_name, filename))
+        for component in AptCnf["%s::Sections" % (config_name)].split():
+            for architecture in AptCnf["%s::Architectures" \
+                                       % (config_name)].split():
+                if architecture != "source":
+                    architecture = "binary-"+architecture
+                target = os.path.join(tree_dir, component, architecture)
+                do_dir(target, "%s, %s, %s" % (tree, component, architecture))
+
+
+################################################################################
+
+def main ():
+    """Initial setup of an archive."""
+
+    global AptCnf, Cnf
+
+    Cnf = utils.get_conf()
+    arguments = [('h', "help", "Init-Dirs::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Init-Dirs::Options::%s" % (i)):
+            Cnf["Init-Dirs::Options::%s" % (i)] = ""
+
+    arguments = apt_pkg.ParseCommandLine(Cnf, arguments, sys.argv)
+
+    options = Cnf.SubTree("Init-Dirs::Options")
+    if options["Help"]:
+        usage()
+    elif arguments:
+        utils.warn("dak init-dirs takes no arguments.")
+        usage(exit_code=1)
+
+    AptCnf = apt_pkg.newConfiguration()
+    apt_pkg.ReadConfigFileISC(AptCnf, utils.which_apt_conf_file())
+
+    create_directories()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/ls.py b/dak/ls.py
new file mode 100755 (executable)
index 0000000..baf1373
--- /dev/null
+++ b/dak/ls.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env python
+
+# Display information about package(s) (suite, version, etc.)
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <aj> ooo, elmo has "special powers"
+# <neuro> ooo, does he have lasers that shoot out of his eyes?
+# <aj> dunno
+# <aj> maybe he can turn invisible? that'd sure help with improved transparency!
+
+################################################################################
+
+import os, pg, sys
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak ls [OPTION] PACKAGE[...]
+Display information about PACKAGE(s).
+
+  -a, --architecture=ARCH    only show info for ARCH(s)
+  -b, --binary-type=TYPE     only show info for binary TYPE
+  -c, --component=COMPONENT  only show info for COMPONENT(s)
+  -g, --greaterorequal       show buildd 'dep-wait pkg >= {highest version}' info
+  -G, --greaterthan          show buildd 'dep-wait pkg >> {highest version}' info
+  -h, --help                 show this help and exit
+  -r, --regex                treat PACKAGE as a regex
+  -s, --suite=SUITE          only show info for this suite
+  -S, --source-and-binary    show info for the binary children of source pkgs
+
+ARCH, COMPONENT and SUITE can be comma (or space) separated lists, e.g.
+    --architecture=m68k,i386"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def main ():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('a', "architecture", "Ls::Options::Architecture", "HasArg"),
+                 ('b', "binarytype", "Ls::Options::BinaryType", "HasArg"),
+                 ('c', "component", "Ls::Options::Component", "HasArg"),
+                 ('f', "format", "Ls::Options::Format", "HasArg"),
+                 ('g', "greaterorequal", "Ls::Options::GreaterOrEqual"),
+                 ('G', "greaterthan", "Ls::Options::GreaterThan"),
+                 ('r', "regex", "Ls::Options::Regex"),
+                 ('s', "suite", "Ls::Options::Suite", "HasArg"),
+                 ('S', "source-and-binary", "Ls::Options::Source-And-Binary"),
+                 ('h', "help", "Ls::Options::Help")]
+    for i in [ "architecture", "binarytype", "component", "format",
+               "greaterorequal", "greaterthan", "regex", "suite",
+               "source-and-binary", "help" ]:
+        if not Cnf.has_key("Ls::Options::%s" % (i)):
+            Cnf["Ls::Options::%s" % (i)] = ""
+
+    packages = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Ls::Options")
+
+    if Options["Help"]:
+        usage()
+    if not packages:
+        utils.fubar("need at least one package name as an argument.")
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    # If cron.daily is running; warn the user that our output might seem strange
+    if os.path.exists(os.path.join(Cnf["Dir::Root"], "Archive_Maintenance_In_Progress")):
+        utils.warn("Archive maintenance is in progress; database inconsistencies are possible.")
+
+    # Handle buildd maintenance helper options
+    if Options["GreaterOrEqual"] or Options["GreaterThan"]:
+        if Options["GreaterOrEqual"] and Options["GreaterThan"]:
+            utils.fubar("-g/--greaterorequal and -G/--greaterthan are mutually exclusive.")
+        if not Options["Suite"]:
+            Options["Suite"] = "unstable"
+
+    # Parse -a/--architecture, -c/--component and -s/--suite
+    (con_suites, con_architectures, con_components, check_source) = \
+                 utils.parse_args(Options)
+
+    if Options["BinaryType"]:
+        if Options["BinaryType"] != "udeb" and Options["BinaryType"] != "deb":
+            utils.fubar("Invalid binary type.  'udeb' and 'deb' recognised.")
+        con_bintype = "AND b.type = '%s'" % (Options["BinaryType"])
+        # REMOVE ME TRAMP
+        if Options["BinaryType"] == "udeb":
+            check_source = 0
+    else:
+        con_bintype = ""
+
+    if Options["Regex"]:
+        comparison_operator = "~"
+    else:
+        comparison_operator = "="
+
+    if Options["Source-And-Binary"]:
+        new_packages = []
+        for package in packages:
+            q = projectB.query("SELECT DISTINCT b.package FROM binaries b, bin_associations ba, suite su, source s WHERE b.source = s.id AND su.id = ba.suite AND b.id = ba.bin AND s.source %s '%s' %s" % (comparison_operator, package, con_suites))
+            new_packages.extend([ i[0] for i in q.getresult() ])
+            if package not in new_packages:
+                new_packages.append(package)
+        packages = new_packages
+
+    results = 0
+    for package in packages:
+        q = projectB.query("""
+SELECT b.package, b.version, a.arch_string, su.suite_name, c.name, m.name
+  FROM binaries b, architecture a, suite su, bin_associations ba,
+       files f, location l, component c, maintainer m
+ WHERE b.package %s '%s' AND a.id = b.architecture AND su.id = ba.suite
+   AND b.id = ba.bin AND b.file = f.id AND f.location = l.id
+   AND l.component = c.id AND b.maintainer = m.id %s %s %s
+""" % (comparison_operator, package, con_suites, con_architectures, con_bintype))
+        ql = q.getresult()
+        if check_source:
+            q = projectB.query("""
+SELECT s.source, s.version, 'source', su.suite_name, c.name, m.name
+  FROM source s, suite su, src_associations sa, files f, location l,
+       component c, maintainer m
+ WHERE s.source %s '%s' AND su.id = sa.suite AND s.id = sa.source
+   AND s.file = f.id AND f.location = l.id AND l.component = c.id
+   AND s.maintainer = m.id %s
+""" % (comparison_operator, package, con_suites))
+            ql.extend(q.getresult())
+        d = {}
+        highver = {}
+        for i in ql:
+            results += 1
+            (pkg, version, architecture, suite, component, maintainer) = i
+            if component != "main":
+                suite = "%s/%s" % (suite, component)
+            if not d.has_key(pkg):
+                d[pkg] = {}
+            highver.setdefault(pkg,"")
+            if not d[pkg].has_key(version):
+                d[pkg][version] = {}
+                if apt_pkg.VersionCompare(version, highver[pkg]) > 0:
+                    highver[pkg] = version
+            if not d[pkg][version].has_key(suite):
+                d[pkg][version][suite] = []
+            d[pkg][version][suite].append(architecture)
+
+        packages = d.keys()
+        packages.sort()
+        for pkg in packages:
+            versions = d[pkg].keys()
+            versions.sort(apt_pkg.VersionCompare)
+            for version in versions:
+                suites = d[pkg][version].keys()
+                suites.sort()
+                for suite in suites:
+                    arches = d[pkg][version][suite]
+                    arches.sort(utils.arch_compare_sw)
+                    if Options["Format"] == "": #normal
+                        sys.stdout.write("%10s | %10s | %13s | " % (pkg, version, suite))
+                        sys.stdout.write(", ".join(arches))
+                        sys.stdout.write('\n')
+                    elif Options["Format"] in [ "control-suite", "heidi" ]:
+                        for arch in arches:
+                            sys.stdout.write("%s %s %s\n" % (pkg, version, arch))
+            if Options["GreaterOrEqual"]:
+                print "\n%s (>= %s)" % (pkg, highver[pkg])
+            if Options["GreaterThan"]:
+                print "\n%s (>> %s)" % (pkg, highver[pkg])
+
+    if not results:
+        sys.exit(1)
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/make_maintainers.py b/dak/make_maintainers.py
new file mode 100755 (executable)
index 0000000..090b8d4
--- /dev/null
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+
+# Generate Maintainers file used by e.g. the Debian Bug Tracking System
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# ``As opposed to "Linux sucks. Respect my academic authoritah, damn
+#   you!" or whatever all this hot air amounts to.''
+#                             -- ajt@ in _that_ thread on debian-devel@
+
+################################################################################
+
+import pg, sys
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+projectB = None
+Cnf = None
+maintainer_from_source_cache = {}
+packages = {}
+fixed_maintainer_cache = {}
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak make-maintainers [OPTION] EXTRA_FILE[...]
+Generate an index of packages <=> Maintainers.
+
+  -h, --help                 show this help and exit
+"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def fix_maintainer (maintainer):
+    global fixed_maintainer_cache
+
+    if not fixed_maintainer_cache.has_key(maintainer):
+        fixed_maintainer_cache[maintainer] = utils.fix_maintainer(maintainer)[0]
+
+    return fixed_maintainer_cache[maintainer]
+
+def get_maintainer (maintainer):
+    return fix_maintainer(database.get_maintainer(maintainer))
+
+def get_maintainer_from_source (source_id):
+    global maintainer_from_source_cache
+
+    if not maintainer_from_source_cache.has_key(source_id):
+        q = projectB.query("SELECT m.name FROM maintainer m, source s WHERE s.id = %s and s.maintainer = m.id" % (source_id))
+        maintainer = q.getresult()[0][0]
+        maintainer_from_source_cache[source_id] = fix_maintainer(maintainer)
+
+    return maintainer_from_source_cache[source_id]
+
+################################################################################
+
+def main():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Make-Maintainers::Options::Help")]
+    if not Cnf.has_key("Make-Maintainers::Options::Help"):
+        Cnf["Make-Maintainers::Options::Help"] = ""
+
+    extra_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Make-Maintainers::Options")
+
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    for suite in Cnf.SubTree("Suite").List():
+        suite = suite.lower()
+        suite_priority = int(Cnf["Suite::%s::Priority" % (suite)])
+
+        # Source packages
+        q = projectB.query("SELECT s.source, s.version, m.name FROM src_associations sa, source s, suite su, maintainer m WHERE su.suite_name = '%s' AND sa.suite = su.id AND sa.source = s.id AND m.id = s.maintainer" % (suite))
+        sources = q.getresult()
+        for source in sources:
+            package = source[0]
+            version = source[1]
+            maintainer = fix_maintainer(source[2])
+            if packages.has_key(package):
+                if packages[package]["priority"] <= suite_priority:
+                    if apt_pkg.VersionCompare(packages[package]["version"], version) < 0:
+                        packages[package] = { "maintainer": maintainer, "priority": suite_priority, "version": version }
+            else:
+                packages[package] = { "maintainer": maintainer, "priority": suite_priority, "version": version }
+
+        # Binary packages
+        q = projectB.query("SELECT b.package, b.source, b.maintainer, b.version FROM bin_associations ba, binaries b, suite s WHERE s.suite_name = '%s' AND ba.suite = s.id AND ba.bin = b.id" % (suite))
+        binaries = q.getresult()
+        for binary in binaries:
+            package = binary[0]
+            source_id = binary[1]
+            version = binary[3]
+            # Use the source maintainer first; falling back on the binary maintainer as a last resort only
+            if source_id:
+                maintainer = get_maintainer_from_source(source_id)
+            else:
+                maintainer = get_maintainer(binary[2])
+            if packages.has_key(package):
+                if packages[package]["priority"] <= suite_priority:
+                    if apt_pkg.VersionCompare(packages[package]["version"], version) < 0:
+                        packages[package] = { "maintainer": maintainer, "priority": suite_priority, "version": version }
+            else:
+                packages[package] = { "maintainer": maintainer, "priority": suite_priority, "version": version }
+
+    # Process any additional Maintainer files (e.g. from pseudo packages)
+    for filename in extra_files:
+        file = utils.open_file(filename)
+        for line in file.readlines():
+            line = utils.re_comments.sub('', line).strip()
+            if line == "":
+                continue
+            split = line.split()
+            lhs = split[0]
+            maintainer = fix_maintainer(" ".join(split[1:]))
+            if lhs.find('~') != -1:
+                (package, version) = lhs.split('~', 1)
+            else:
+                package = lhs
+                version = '*'
+            # A version of '*' overwhelms all real version numbers
+            if not packages.has_key(package) or version == '*' \
+               or apt_pkg.VersionCompare(packages[package]["version"], version) < 0:
+                packages[package] = { "maintainer": maintainer, "version": version }
+        file.close()
+
+    package_keys = packages.keys()
+    package_keys.sort()
+    for package in package_keys:
+        lhs = "~".join([package, packages[package]["version"]])
+        print "%-30s %s" % (lhs, packages[package]["maintainer"])
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/make_overrides.py b/dak/make_overrides.py
new file mode 100755 (executable)
index 0000000..1087ce2
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+
+# Output override files for apt-ftparchive and indices/
+# Copyright (C) 2000, 2001, 2002, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# This is seperate because it's horribly Debian specific and I don't
+# want that kind of horribleness in the otherwise generic 'dak
+# make-overrides'.  It does duplicate code tho.
+
+################################################################################
+
+import pg, sys
+import apt_pkg
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+override = {}
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak make-overrides
+Outputs the override tables to text files.
+
+  -h, --help                show this help and exit."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def do_list(output_file, suite, component, otype):
+    global override
+
+    suite_id = database.get_suite_id(suite)
+    if suite_id == -1:
+        utils.fubar("Suite '%s' not recognised." % (suite))
+
+    component_id = database.get_component_id(component)
+    if component_id == -1:
+        utils.fubar("Component '%s' not recognised." % (component))
+
+    otype_id = database.get_override_type_id(otype)
+    if otype_id == -1:
+        utils.fubar("Type '%s' not recognised. (Valid types are deb, udeb and dsc)" % (otype))
+
+    override.setdefault(suite, {})
+    override[suite].setdefault(component, {})
+    override[suite][component].setdefault(otype, {})
+
+    if otype == "dsc":
+        q = projectB.query("SELECT o.package, s.section, o.maintainer FROM override o, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.section = s.id ORDER BY s.section, o.package" % (suite_id, component_id, otype_id))
+        for i in q.getresult():
+            override[suite][component][otype][i[0]] = i
+            output_file.write(utils.result_join(i)+'\n')
+    else:
+        q = projectB.query("SELECT o.package, p.priority, s.section, o.maintainer, p.level FROM override o, priority p, section s WHERE o.suite = %s AND o.component = %s AND o.type = %s AND o.priority = p.id AND o.section = s.id ORDER BY s.section, p.level, o.package" % (suite_id, component_id, otype_id))
+        for i in q.getresult():
+            i = i[:-1]; # Strip the priority level
+            override[suite][component][otype][i[0]] = i
+            output_file.write(utils.result_join(i)+'\n')
+
+################################################################################
+
+def main ():
+    global Cnf, projectB, override
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Make-Overrides::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Make-Overrides::Options::%s" % (i)):
+            Cnf["Make-Overrides::Options::%s" % (i)] = ""
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Make-Overrides::Options")
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    for suite in Cnf.SubTree("Check-Overrides::OverrideSuites").List():
+        if Cnf.has_key("Suite::%s::Untouchable" % suite) and Cnf["Suite::%s::Untouchable" % suite] != 0:
+            continue
+        suite = suite.lower()
+
+        sys.stderr.write("Processing %s...\n" % (suite))
+        override_suite = Cnf["Suite::%s::OverrideCodeName" % (suite)]
+        for component in Cnf.SubTree("Component").List():
+            if component == "mixed":
+                continue; # Ick
+            for otype in Cnf.ValueList("OverrideType"):
+                if otype == "deb":
+                    suffix = ""
+                elif otype == "udeb":
+                    if component == "contrib":
+                        continue; # Ick2
+                    suffix = ".debian-installer"
+                elif otype == "dsc":
+                    suffix = ".src"
+                filename = "%s/override.%s.%s%s" % (Cnf["Dir::Override"], override_suite, component, suffix)
+                output_file = utils.open_file(filename, 'w')
+                do_list(output_file, suite, component, otype)
+                output_file.close()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/make_suite_file_list.py b/dak/make_suite_file_list.py
new file mode 100755 (executable)
index 0000000..e366438
--- /dev/null
@@ -0,0 +1,436 @@
+#!/usr/bin/env python
+
+# Generate file lists used by apt-ftparchive to generate Packages and Sources files
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <elmo> I'm doing it in python btw.. nothing against your monster
+#        SQL, but the python wins in terms of speed and readiblity
+# <aj> bah
+# <aj> you suck!!!!!
+# <elmo> sorry :(
+# <aj> you are not!!!
+# <aj> you mock my SQL!!!!
+# <elmo> you want have contest of skillz??????
+# <aj> all your skillz are belong to my sql!!!!
+# <elmo> yo momma are belong to my python!!!!
+# <aj> yo momma was SQLin' like a pig last night!
+
+################################################################################
+
+import copy, os, pg, sys
+import apt_pkg
+import symlink_dists
+from daklib import database
+from daklib import logging
+from daklib import utils
+
+################################################################################
+
+projectB = None
+Cnf = None
+Logger = None
+Options = None
+
+################################################################################
+
+def Dict(**dict): return dict
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak make-suite-file-list [OPTION]
+Write out file lists suitable for use with apt-ftparchive.
+
+  -a, --architecture=ARCH   only write file lists for this architecture
+  -c, --component=COMPONENT only write file lists for this component
+  -f, --force               ignore Untouchable suite directives in dak.conf
+  -h, --help                show this help and exit
+  -n, --no-delete           don't delete older versions
+  -s, --suite=SUITE         only write file lists for this suite
+
+ARCH, COMPONENT and SUITE can be space separated lists, e.g.
+    --architecture=\"m68k i386\""""
+    sys.exit(exit_code)
+
+################################################################################
+
+def version_cmp(a, b):
+    return -apt_pkg.VersionCompare(a[0], b[0])
+
+#####################################################
+
+def delete_packages(delete_versions, pkg, dominant_arch, suite,
+                    dominant_version, delete_table, delete_col, packages):
+    suite_id = database.get_suite_id(suite)
+    for version in delete_versions:
+        delete_unique_id = version[1]
+        if not packages.has_key(delete_unique_id):
+            continue
+        delete_version = version[0]
+        delete_id = packages[delete_unique_id]["id"]
+        delete_arch = packages[delete_unique_id]["arch"]
+        if not Cnf.Find("Suite::%s::Untouchable" % (suite)) or Options["Force"]:
+            if Options["No-Delete"]:
+                print "Would delete %s_%s_%s in %s in favour of %s_%s" % (pkg, delete_arch, delete_version, suite, dominant_version, dominant_arch)
+            else:
+                Logger.log(["dominated", pkg, delete_arch, delete_version, dominant_version, dominant_arch])
+                projectB.query("DELETE FROM %s WHERE suite = %s AND %s = %s" % (delete_table, suite_id, delete_col, delete_id))
+            del packages[delete_unique_id]
+        else:
+            if Options["No-Delete"]:
+                print "Would delete %s_%s_%s in favour of %s_%s, but %s is untouchable" % (pkg, delete_arch, delete_version, dominant_version, dominant_arch, suite)
+            else:
+                Logger.log(["dominated but untouchable", pkg, delete_arch, delete_version, dominant_version, dominant_arch])
+
+#####################################################
+
+# Per-suite&pkg: resolve arch-all, vs. arch-any, assumes only one arch-all
+def resolve_arch_all_vs_any(versions, packages):
+    arch_all_version = None
+    arch_any_versions = copy.copy(versions)
+    for i in arch_any_versions:
+        unique_id = i[1]
+        arch = packages[unique_id]["arch"]
+        if arch == "all":
+            arch_all_versions = [i]
+            arch_all_version = i[0]
+            arch_any_versions.remove(i)
+    # Sort arch: any versions into descending order
+    arch_any_versions.sort(version_cmp)
+    highest_arch_any_version = arch_any_versions[0][0]
+
+    pkg = packages[unique_id]["pkg"]
+    suite = packages[unique_id]["suite"]
+    delete_table = "bin_associations"
+    delete_col = "bin"
+
+    if apt_pkg.VersionCompare(highest_arch_any_version, arch_all_version) < 1:
+        # arch: all dominates
+        delete_packages(arch_any_versions, pkg, "all", suite,
+                        arch_all_version, delete_table, delete_col, packages)
+    else:
+        # arch: any dominates
+        delete_packages(arch_all_versions, pkg, "any", suite,
+                        highest_arch_any_version, delete_table, delete_col,
+                        packages)
+
+#####################################################
+
+# Per-suite&pkg&arch: resolve duplicate versions
+def remove_duplicate_versions(versions, packages):
+    # Sort versions into descending order
+    versions.sort(version_cmp)
+    dominant_versions = versions[0]
+    dominated_versions = versions[1:]
+    (dominant_version, dominant_unqiue_id) = dominant_versions
+    pkg = packages[dominant_unqiue_id]["pkg"]
+    arch = packages[dominant_unqiue_id]["arch"]
+    suite = packages[dominant_unqiue_id]["suite"]
+    if arch == "source":
+        delete_table = "src_associations"
+        delete_col = "source"
+    else: # !source
+        delete_table = "bin_associations"
+        delete_col = "bin"
+    # Remove all but the highest
+    delete_packages(dominated_versions, pkg, arch, suite,
+                    dominant_version, delete_table, delete_col, packages)
+    return [dominant_versions]
+
+################################################################################
+
+def cleanup(packages):
+    # Build up the index used by the clean up functions
+    d = {}
+    for unique_id in packages.keys():
+        suite = packages[unique_id]["suite"]
+        pkg = packages[unique_id]["pkg"]
+        arch = packages[unique_id]["arch"]
+        version = packages[unique_id]["version"]
+        d.setdefault(suite, {})
+        d[suite].setdefault(pkg, {})
+        d[suite][pkg].setdefault(arch, [])
+        d[suite][pkg][arch].append([version, unique_id])
+    # Clean up old versions
+    for suite in d.keys():
+        for pkg in d[suite].keys():
+            for arch in d[suite][pkg].keys():
+                versions = d[suite][pkg][arch]
+                if len(versions) > 1:
+                    d[suite][pkg][arch] = remove_duplicate_versions(versions, packages)
+
+    # Arch: all -> any and vice versa
+    for suite in d.keys():
+        for pkg in d[suite].keys():
+            arches = d[suite][pkg]
+            # If we don't have any arch: all; we've nothing to do
+            if not arches.has_key("all"):
+                continue
+            # Check to see if we have arch: all and arch: !all (ignoring source)
+            num_arches = len(arches.keys())
+            if arches.has_key("source"):
+                num_arches -= 1
+            # If we do, remove the duplicates
+            if num_arches > 1:
+                versions = []
+                for arch in arches.keys():
+                    if arch != "source":
+                        versions.extend(d[suite][pkg][arch])
+                resolve_arch_all_vs_any(versions, packages)
+
+################################################################################
+
+def write_legacy_mixed_filelist(suite, list, packages, dislocated_files):
+    # Work out the filename
+    filename = os.path.join(Cnf["Dir::Lists"], "%s_-_all.list" % (suite))
+    output = utils.open_file(filename, "w")
+    # Generate the final list of files
+    files = {}
+    for id in list:
+        path = packages[id]["path"]
+        filename = packages[id]["filename"]
+        file_id = packages[id]["file_id"]
+        if suite == "stable" and dislocated_files.has_key(file_id):
+            filename = dislocated_files[file_id]
+        else:
+            filename = path + filename
+        if files.has_key(filename):
+            utils.warn("%s (in %s) is duplicated." % (filename, suite))
+        else:
+            files[filename] = ""
+    # Sort the files since apt-ftparchive doesn't
+    keys = files.keys()
+    keys.sort()
+    # Write the list of files out
+    for file in keys:
+        output.write(file+'\n')
+    output.close()
+
+############################################################
+
+def write_filelist(suite, component, arch, type, list, packages, dislocated_files):
+    # Work out the filename
+    if arch != "source":
+        if type == "udeb":
+            arch = "debian-installer_binary-%s" % (arch)
+        elif type == "deb":
+            arch = "binary-%s" % (arch)
+    filename = os.path.join(Cnf["Dir::Lists"], "%s_%s_%s.list" % (suite, component, arch))
+    output = utils.open_file(filename, "w")
+    # Generate the final list of files
+    files = {}
+    for id in list:
+        path = packages[id]["path"]
+        filename = packages[id]["filename"]
+        file_id = packages[id]["file_id"]
+        pkg = packages[id]["pkg"]
+        if suite == "stable" and dislocated_files.has_key(file_id):
+            filename = dislocated_files[file_id]
+        else:
+            filename = path + filename
+        if files.has_key(pkg):
+            utils.warn("%s (in %s/%s, %s) is duplicated." % (pkg, suite, component, filename))
+        else:
+            files[pkg] = filename
+    # Sort the files since apt-ftparchive doesn't
+    pkgs = files.keys()
+    pkgs.sort()
+    # Write the list of files out
+    for pkg in pkgs:
+        output.write(files[pkg]+'\n')
+    output.close()
+
+################################################################################
+
+def write_filelists(packages, dislocated_files):
+    # Build up the index to iterate over
+    d = {}
+    for unique_id in packages.keys():
+        suite = packages[unique_id]["suite"]
+        component = packages[unique_id]["component"]
+        arch = packages[unique_id]["arch"]
+        type = packages[unique_id]["type"]
+        d.setdefault(suite, {})
+        d[suite].setdefault(component, {})
+        d[suite][component].setdefault(arch, {})
+        d[suite][component][arch].setdefault(type, [])
+        d[suite][component][arch][type].append(unique_id)
+    # Flesh out the index
+    if not Options["Suite"]:
+        suites = Cnf.SubTree("Suite").List()
+    else:
+        suites = utils.split_args(Options["Suite"])
+    for suite in [ i.lower() for i in suites ]:
+        d.setdefault(suite, {})
+        if not Options["Component"]:
+            components = Cnf.ValueList("Suite::%s::Components" % (suite))
+        else:
+            components = utils.split_args(Options["Component"])
+        udeb_components = Cnf.ValueList("Suite::%s::UdebComponents" % (suite))
+        udeb_components = udeb_components
+        for component in components:
+            d[suite].setdefault(component, {})
+            if component in udeb_components:
+                binary_types = [ "deb", "udeb" ]
+            else:
+                binary_types = [ "deb" ]
+            if not Options["Architecture"]:
+                architectures = Cnf.ValueList("Suite::%s::Architectures" % (suite))
+            else:
+                architectures = utils.split_args(Options["Architectures"])
+            for arch in [ i.lower() for i in architectures ]:
+                d[suite][component].setdefault(arch, {})
+                if arch == "source":
+                    types = [ "dsc" ]
+                else:
+                    types = binary_types
+                for type in types:
+                    d[suite][component][arch].setdefault(type, [])
+    # Then walk it
+    for suite in d.keys():
+        if Cnf.has_key("Suite::%s::Components" % (suite)):
+            for component in d[suite].keys():
+                for arch in d[suite][component].keys():
+                    if arch == "all":
+                        continue
+                    for type in d[suite][component][arch].keys():
+                        list = d[suite][component][arch][type]
+                        # If it's a binary, we need to add in the arch: all debs too
+                        if arch != "source":
+                            archall_suite = Cnf.get("Make-Suite-File-List::ArchAllMap::%s" % (suite))
+                            if archall_suite:
+                                list.extend(d[archall_suite][component]["all"][type])
+                            elif d[suite][component].has_key("all") and \
+                                     d[suite][component]["all"].has_key(type):
+                                list.extend(d[suite][component]["all"][type])
+                        write_filelist(suite, component, arch, type, list,
+                                       packages, dislocated_files)
+        else: # legacy-mixed suite
+            list = []
+            for component in d[suite].keys():
+                for arch in d[suite][component].keys():
+                    for type in d[suite][component][arch].keys():
+                        list.extend(d[suite][component][arch][type])
+            write_legacy_mixed_filelist(suite, list, packages, dislocated_files)
+
+################################################################################
+
+# Want to use stable dislocation support: True or false?
+def stable_dislocation_p():
+    # If the support is not explicitly enabled, assume it's disabled
+    if not Cnf.FindB("Dinstall::StableDislocationSupport"):
+        return 0
+    # If we don't have a stable suite, obviously a no-op
+    if not Cnf.has_key("Suite::Stable"):
+        return 0
+    # If the suite(s) weren't explicitly listed, all suites are done
+    if not Options["Suite"]:
+        return 1
+    # Otherwise, look in what suites the user specified
+    suites = utils.split_args(Options["Suite"])
+
+    if "stable" in suites:
+        return 1
+    else:
+        return 0
+
+################################################################################
+
+def do_da_do_da():
+    # If we're only doing a subset of suites, ensure we do enough to
+    # be able to do arch: all mapping.
+    if Options["Suite"]:
+        suites = utils.split_args(Options["Suite"])
+        for suite in suites:
+            archall_suite = Cnf.get("Make-Suite-File-List::ArchAllMap::%s" % (suite))
+            if archall_suite and archall_suite not in suites:
+                utils.warn("Adding %s as %s maps Arch: all from it." % (archall_suite, suite))
+                suites.append(archall_suite)
+        Options["Suite"] = ",".join(suites)
+
+    (con_suites, con_architectures, con_components, check_source) = \
+                 utils.parse_args(Options)
+
+    if stable_dislocation_p():
+        dislocated_files = symlink_dists.find_dislocated_stable(Cnf, projectB)
+    else:
+        dislocated_files = {}
+
+    query = """
+SELECT b.id, b.package, a.arch_string, b.version, l.path, f.filename, c.name,
+       f.id, su.suite_name, b.type
+  FROM binaries b, bin_associations ba, architecture a, files f, location l,
+       component c, suite su
+  WHERE b.id = ba.bin AND b.file = f.id AND b.architecture = a.id
+    AND f.location = l.id AND l.component = c.id AND ba.suite = su.id
+    %s %s %s""" % (con_suites, con_architectures, con_components)
+    if check_source:
+        query += """
+UNION
+SELECT s.id, s.source, 'source', s.version, l.path, f.filename, c.name, f.id,
+       su.suite_name, 'dsc'
+  FROM source s, src_associations sa, files f, location l, component c, suite su
+  WHERE s.id = sa.source AND s.file = f.id AND f.location = l.id
+    AND l.component = c.id AND sa.suite = su.id %s %s""" % (con_suites, con_components)
+    q = projectB.query(query)
+    ql = q.getresult()
+    # Build up the main index of packages
+    packages = {}
+    unique_id = 0
+    for i in ql:
+        (id, pkg, arch, version, path, filename, component, file_id, suite, type) = i
+        # 'id' comes from either 'binaries' or 'source', so it's not unique
+        unique_id += 1
+        packages[unique_id] = Dict(id=id, pkg=pkg, arch=arch, version=version,
+                                   path=path, filename=filename,
+                                   component=component, file_id=file_id,
+                                   suite=suite, type = type)
+    cleanup(packages)
+    write_filelists(packages, dislocated_files)
+
+################################################################################
+
+def main():
+    global Cnf, projectB, Options, Logger
+
+    Cnf = utils.get_conf()
+    Arguments = [('a', "architecture", "Make-Suite-File-List::Options::Architecture", "HasArg"),
+                 ('c', "component", "Make-Suite-File-List::Options::Component", "HasArg"),
+                 ('h', "help", "Make-Suite-File-List::Options::Help"),
+                 ('n', "no-delete", "Make-Suite-File-List::Options::No-Delete"),
+                 ('f', "force", "Make-Suite-File-List::Options::Force"),
+                 ('s', "suite", "Make-Suite-File-List::Options::Suite", "HasArg")]
+    for i in ["architecture", "component", "help", "no-delete", "suite", "force" ]:
+        if not Cnf.has_key("Make-Suite-File-List::Options::%s" % (i)):
+            Cnf["Make-Suite-File-List::Options::%s" % (i)] = ""
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Make-Suite-File-List::Options")
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+    Logger = logging.Logger(Cnf, "make-suite-file-list")
+    do_da_do_da()
+    Logger.close()
+
+#########################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/mirror_split.py b/dak/mirror_split.py
new file mode 100644 (file)
index 0000000..3f79020
--- /dev/null
@@ -0,0 +1,391 @@
+#!/usr/bin/env python
+
+# Prepare and maintain partial trees by architecture
+# Copyright (C) 2004, 2006  Daniel Silverstone <dsilvers@digital-scurf.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+###############################################################################
+## <kinnison> So Martin, do you have a quote for me yet?
+## <tbm> Make something damned stupid up and attribute it to me, that's okay
+###############################################################################
+
+import sys
+import apt_pkg
+
+from stat import S_ISDIR, S_ISLNK, S_ISREG
+import os
+import cPickle
+
+import daklib.utils
+
+## Master path is the main repository
+#MASTER_PATH = "/org/ftp.debian.org/scratch/dsilvers/master"
+
+MASTER_PATH = "***Configure Mirror-Split::FTPPath Please***"
+TREE_ROOT = "***Configure Mirror-Split::TreeRootPath Please***"
+TREE_DB_ROOT = "***Configure Mirror-Split::TreeDatabasePath Please***"
+trees = []
+
+Cnf = None
+
+###############################################################################
+# A MirrorSplitTarget is a representation of a target. It is a set of archs, a path
+# and whether or not the target includes source.
+##################
+
+class MirrorSplitTarget:
+    def __init__(self, name, archs, source):
+        self.name = name
+        self.root = "%s/%s" % (TREE_ROOT,name)
+        self.archs = archs.split(",")
+        self.source = source
+        self.dbpath = "%s/%s.db" % (TREE_DB_ROOT,name)
+        self.db = MirrorSplitDB()
+        if os.path.exists( self.dbpath ):
+            self.db.load_from_file( self.dbpath )
+
+    ## Save the db back to disk
+    def save_db(self):
+        self.db.save_to_file( self.dbpath )
+
+    ## Returns true if it's a poolish match
+    def poolish_match(self, path):
+        for a in self.archs:
+            if path.endswith( "_%s.deb" % (a) ):
+                return 1
+            if path.endswith( "_%s.udeb" % (a) ):
+                return 1
+        if self.source:
+            if (path.endswith( ".tar.gz" ) or
+                path.endswith( ".diff.gz" ) or
+                path.endswith( ".dsc" )):
+                return 1
+        return 0
+
+    ## Returns false if it's a badmatch distswise
+    def distish_match(self,path):
+        for a in self.archs:
+            if path.endswith("/Contents-%s.gz" % (a)):
+                return 1
+            if path.find("/binary-%s/" % (a)) != -1:
+                return 1
+            if path.find("/installer-%s/" % (a)) != -1:
+                return 1
+        if path.find("/source/") != -1:
+            if self.source:
+                return 1
+            else:
+                return 0
+        if path.find("/Contents-") != -1:
+            return 0
+        if path.find("/binary-") != -1:
+            return 0
+        if path.find("/installer-") != -1:
+            return 0
+        return 1
+
+##############################################################################
+# The applicable function is basically a predicate. Given a path and a
+# target object its job is to decide if the path conforms for the
+# target and thus is wanted.
+#
+# 'verbatim' is a list of files which are copied regardless
+# it should be loaded from a config file eventually
+##################
+
+verbatim = [
+    ]
+
+verbprefix = [
+    "/tools/",
+    "/README",
+    "/doc/"
+    ]
+
+def applicable(path, target):
+    if path.startswith("/pool/"):
+        return target.poolish_match(path)
+    if (path.startswith("/dists/") or
+        path.startswith("/project/experimental/")):
+        return target.distish_match(path)
+    if path in verbatim:
+        return 1
+    for prefix in verbprefix:
+        if path.startswith(prefix):
+            return 1
+    return 0
+
+
+##############################################################################
+# A MirrorSplitDir is a representation of a tree.
+#   It distinguishes files dirs and links
+# Dirs are dicts of (name, MirrorSplitDir)
+# Files are dicts of (name, inode)
+# Links are dicts of (name, target)
+##############
+
+class MirrorSplitDir:
+    def __init__(self):
+        self.dirs = {}
+        self.files = {}
+        self.links = {}
+
+##############################################################################
+# A MirrorSplitDB is a container for a MirrorSplitDir...
+##############
+
+class MirrorSplitDB:
+    ## Initialise a MirrorSplitDB as containing nothing
+    def __init__(self):
+        self.root = MirrorSplitDir()
+
+    def _internal_recurse(self, path):
+        bdir = MirrorSplitDir()
+        dl = os.listdir( path )
+        dl.sort()
+        dirs = []
+        for ln in dl:
+            lnl = os.lstat( "%s/%s" % (path, ln) )
+            if S_ISDIR(lnl[0]):
+                dirs.append(ln)
+            elif S_ISLNK(lnl[0]):
+                bdir.links[ln] = os.readlink( "%s/%s" % (path, ln) )
+            elif S_ISREG(lnl[0]):
+                bdir.files[ln] = lnl[1]
+            else:
+                daklib.utils.fubar( "Confused by %s/%s -- not a dir, link or file" %
+                            ( path, ln ) )
+        for d in dirs:
+            bdir.dirs[d] = self._internal_recurse( "%s/%s" % (path,d) )
+
+        return bdir
+
+    ## Recurse through a given path, setting the sequence accordingly
+    def init_from_dir(self, dirp):
+        self.root = self._internal_recurse( dirp )
+
+    ## Load this MirrorSplitDB from file
+    def load_from_file(self, fname):
+        f = open(fname, "r")
+        self.root = cPickle.load(f)
+        f.close()
+
+    ## Save this MirrorSplitDB to a file
+    def save_to_file(self, fname):
+        f = open(fname, "w")
+        cPickle.dump( self.root, f, 1 )
+        f.close()
+
+
+##############################################################################
+# Helper functions for the tree syncing...
+##################
+
+def _pth(a,b):
+    return "%s/%s" % (a,b)
+
+def do_mkdir(targ,path):
+    if not os.path.exists( _pth(targ.root, path) ):
+        os.makedirs( _pth(targ.root, path) )
+
+def do_mkdir_f(targ,path):
+    do_mkdir(targ, os.path.dirname(path))
+
+def do_link(targ,path):
+    do_mkdir_f(targ,path)
+    os.link( _pth(MASTER_PATH, path),
+             _pth(targ.root, path))
+
+def do_symlink(targ,path,link):
+    do_mkdir_f(targ,path)
+    os.symlink( link, _pth(targ.root, path) )
+
+def do_unlink(targ,path):
+    os.unlink( _pth(targ.root, path) )
+
+def do_unlink_dir(targ,path):
+    os.system( "rm -Rf '%s'" % _pth(targ.root, path) )
+
+##############################################################################
+# Reconciling a target with the sourcedb
+################
+
+def _internal_reconcile( path, srcdir, targdir, targ ):
+    # Remove any links in targdir which aren't in srcdir
+    # Or which aren't applicable
+    rm = []
+    for k in targdir.links.keys():
+        if applicable( _pth(path, k), targ ):
+            if not srcdir.links.has_key(k):
+                rm.append(k)
+        else:
+            rm.append(k)
+    for k in rm:
+        #print "-L-", _pth(path,k)
+        do_unlink(targ, _pth(path,k))
+        del targdir.links[k]
+
+    # Remove any files in targdir which aren't in srcdir
+    # Or which aren't applicable
+    rm = []
+    for k in targdir.files.keys():
+        if applicable( _pth(path, k), targ ):
+            if not srcdir.files.has_key(k):
+                rm.append(k)
+        else:
+            rm.append(k)
+    for k in rm:
+        #print "-F-", _pth(path,k)
+        do_unlink(targ, _pth(path,k))
+        del targdir.files[k]
+
+    # Remove any dirs in targdir which aren't in srcdir
+    rm = []
+    for k in targdir.dirs.keys():
+        if not srcdir.dirs.has_key(k):
+            rm.append(k)
+    for k in rm:
+        #print "-D-", _pth(path,k)
+        do_unlink_dir(targ, _pth(path,k))
+        del targdir.dirs[k]
+
+    # Add/update files
+    for k in srcdir.files.keys():
+        if applicable( _pth(path,k), targ ):
+            if not targdir.files.has_key(k):
+                #print "+F+", _pth(path,k)
+                do_link( targ, _pth(path,k) )
+                targdir.files[k] = srcdir.files[k]
+            else:
+                if targdir.files[k] != srcdir.files[k]:
+                    #print "*F*", _pth(path,k)
+                    do_unlink( targ, _pth(path,k) )
+                    do_link( targ, _pth(path,k) )
+                    targdir.files[k] = srcdir.files[k]
+
+    # Add/update links
+    for k in srcdir.links.keys():
+        if applicable( _pth(path,k), targ ):
+            if not targdir.links.has_key(k):
+                targdir.links[k] = srcdir.links[k];
+                #print "+L+",_pth(path,k), "->", srcdir.links[k]
+                do_symlink( targ, _pth(path,k), targdir.links[k] )
+            else:
+                if targdir.links[k] != srcdir.links[k]:
+                    do_unlink( targ, _pth(path,k) )
+                    targdir.links[k] = srcdir.links[k]
+                    #print "*L*", _pth(path,k), "to ->", srcdir.links[k]
+                    do_symlink( targ, _pth(path,k), targdir.links[k] )
+
+    # Do dirs
+    for k in srcdir.dirs.keys():
+        if not targdir.dirs.has_key(k):
+            targdir.dirs[k] = MirrorSplitDir()
+            #print "+D+", _pth(path,k)
+        _internal_reconcile( _pth(path,k), srcdir.dirs[k],
+                             targdir.dirs[k], targ )
+
+
+def reconcile_target_db( src, targ ):
+    _internal_reconcile( "", src.root, targ.db.root, targ )
+
+###############################################################################
+
+def load_config():
+    global MASTER_PATH
+    global TREE_ROOT
+    global TREE_DB_ROOT
+    global trees
+
+    MASTER_PATH = Cnf["Mirror-Split::FTPPath"]
+    TREE_ROOT = Cnf["Mirror-Split::TreeRootPath"]
+    TREE_DB_ROOT = Cnf["Mirror-Split::TreeDatabasePath"]
+
+    for a in Cnf.ValueList("Mirror-Split::BasicTrees"):
+        trees.append( MirrorSplitTarget( a, "%s,all" % a, 1 ) )
+
+    for n in Cnf.SubTree("Mirror-Split::CombinationTrees").List():
+        archs = Cnf.ValueList("Mirror-Split::CombinationTrees::%s" % n)
+        source = 0
+        if "source" in archs:
+            source = 1
+            archs.remove("source")
+        archs = ",".join(archs)
+        trees.append( MirrorSplitTarget( n, archs, source ) )
+
+def do_list ():
+    print "Master path",MASTER_PATH
+    print "Trees at",TREE_ROOT
+    print "DBs at",TREE_DB_ROOT
+
+    for tree in trees:
+        print tree.name,"contains",", ".join(tree.archs),
+        if tree.source:
+            print " [source]"
+        else:
+            print ""
+
+def do_help ():
+    print """Usage: dak mirror-split [OPTIONS]
+Generate hardlink trees of certain architectures
+
+  -h, --help                 show this help and exit
+  -l, --list                 list the configuration and exit
+"""
+
+
+def main ():
+    global Cnf
+
+    Cnf = daklib.utils.get_conf()
+
+    Arguments = [('h',"help","Mirror-Split::Options::Help"),
+                 ('l',"list","Mirror-Split::Options::List"),
+                 ]
+
+    arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Cnf["Mirror-Split::Options::cake"] = ""
+    Options = Cnf.SubTree("Mirror-Split::Options")
+
+    print "Loading configuration..."
+    load_config()
+    print "Loaded."
+
+    if Options.has_key("Help"):
+        do_help()
+        return
+    if Options.has_key("List"):
+        do_list()
+        return
+
+
+    src = MirrorSplitDB()
+    print "Scanning", MASTER_PATH
+    src.init_from_dir(MASTER_PATH)
+    print "Scanned"
+
+    for tree in trees:
+        print "Reconciling tree:",tree.name
+        reconcile_target_db( src, tree )
+        print "Saving updated DB...",
+        tree.save_db()
+        print "Done"
+
+##############################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/new_security_install.py b/dak/new_security_install.py
new file mode 100755 (executable)
index 0000000..856428e
--- /dev/null
@@ -0,0 +1,674 @@
+#!/usr/bin/env python
+
+# Wrapper for Debian Security team
+# Copyright (C) 2006  Anthony Towns <ajt@debian.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+################################################################################
+
+from daklib import queue
+from daklib import logging
+from daklib import utils
+from daklib import database
+import apt_pkg, os, sys, pwd, time, re, commands
+
+re_taint_free = re.compile(r"^['/;\-\+\.~\s\w]+$");
+
+Cnf = None
+Options = None
+Upload = None
+Logger = None
+
+advisory = None
+changes = []
+srcverarches = {}
+
+def init():
+    global Cnf, Upload, Options, Logger
+
+    Cnf = utils.get_conf()
+    Cnf["Dinstall::Options::No-Mail"] = "y"
+    Arguments = [('h', "help", "Security-Install::Options::Help"),
+                 ('a', "automatic", "Security-Install::Options::Automatic"),
+                 ('n', "no-action", "Security-Install::Options::No-Action"),
+                 ('s', "sudo", "Security-Install::Options::Sudo"),
+                 (' ', "no-upload", "Security-Install::Options::No-Upload"),
+                 ('u', "fg-upload", "Security-Install::Options::Foreground-Upload"),
+                 (' ', "drop-advisory", "Security-Install::Options::Drop-Advisory"),
+                 ('A', "approve", "Security-Install::Options::Approve"),
+                 ('R', "reject", "Security-Install::Options::Reject"),
+                 ('D', "disembargo", "Security-Install::Options::Disembargo") ]
+
+    for i in Arguments:
+        Cnf[i[2]] = ""
+
+    arguments = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Security-Install::Options")
+
+    whoami = os.getuid()
+    whoamifull = pwd.getpwuid(whoami)
+    username = whoamifull[0]
+    if username != "dak":
+        print "Non-dak user: %s" % username
+        Options["Sudo"] = "y"
+
+    if Options["Help"]:
+        print "help yourself"
+        sys.exit(0)
+
+    if len(arguments) == 0:
+        utils.fubar("Process what?")
+
+    Upload = queue.Upload(Cnf)
+    if Options["No-Action"]:
+        Options["Sudo"] = ""
+    if not Options["Sudo"] and not Options["No-Action"]:
+        Logger = Upload.Logger = logging.Logger(Cnf, "new-security-install")
+
+    return arguments
+
+def quit():
+    if Logger:
+        Logger.close()
+    sys.exit(0)
+
+def load_args(arguments):
+    global advisory, changes
+
+    adv_ids = {}
+    if not arguments[0].endswith(".changes"):
+        adv_ids [arguments[0]] = 1
+        arguments = arguments[1:]
+
+    null_adv_changes = []
+
+    changesfiles = {}
+    for a in arguments:
+        if "/" in a:
+            utils.fubar("can only deal with files in the current directory")
+        if not a.endswith(".changes"):
+            utils.fubar("not a .changes file: %s" % (a))
+        Upload.init_vars()
+        Upload.pkg.changes_file = a
+        Upload.update_vars()
+        if "adv id" in Upload.pkg.changes:
+            changesfiles[a] = 1
+            adv_ids[Upload.pkg.changes["adv id"]] = 1
+        else:
+            null_adv_changes.append(a)
+
+    adv_ids = adv_ids.keys()
+    if len(adv_ids) > 1:
+        utils.fubar("multiple advisories selected: %s" % (", ".join(adv_ids)))
+    if adv_ids == []:
+        advisory = None
+    else:
+        advisory = adv_ids[0]
+
+    changes = changesfiles.keys()
+    return null_adv_changes
+
+def load_adv_changes():
+    global srcverarches, changes
+
+    for c in os.listdir("."):
+        if not c.endswith(".changes"): continue
+        Upload.init_vars()
+        Upload.pkg.changes_file = c
+        Upload.update_vars()
+        if "adv id" not in Upload.pkg.changes:
+            continue
+        if Upload.pkg.changes["adv id"] != advisory:
+            continue
+
+        if c not in changes: changes.append(c)
+        srcver = "%s %s" % (Upload.pkg.changes["source"],
+                            Upload.pkg.changes["version"])
+        srcverarches.setdefault(srcver, {})
+        for arch in Upload.pkg.changes["architecture"].keys():
+            srcverarches[srcver][arch] = 1
+
+def advisory_info():
+    if advisory != None:
+        print "Advisory: %s" % (advisory)
+    print "Changes:"
+    for c in changes:
+        print " %s" % (c)
+
+    print "Packages:"
+    svs = srcverarches.keys()
+    svs.sort()
+    for sv in svs:
+        as = srcverarches[sv].keys()
+        as.sort()
+        print " %s (%s)" % (sv, ", ".join(as))
+
+def prompt(opts, default):
+    p = ""
+    v = {}
+    for o in opts:
+        v[o[0].upper()] = o
+        if o[0] == default:
+            p += ", [%s]%s" % (o[0], o[1:])
+        else:
+            p += ", " + o
+    p = p[2:] + "? "
+    a = None
+
+    if Options["Automatic"]:
+        a = default
+
+    while a not in v:
+        a = utils.our_raw_input(p) + default
+        a = a[:1].upper()
+
+    return v[a]
+
+def add_changes(extras):
+    for c in extras:
+        changes.append(c)
+        Upload.init_vars()
+        Upload.pkg.changes_file = c
+        Upload.update_vars()
+        srcver = "%s %s" % (Upload.pkg.changes["source"], Upload.pkg.changes["version"])
+        srcverarches.setdefault(srcver, {})
+        for arch in Upload.pkg.changes["architecture"].keys():
+            srcverarches[srcver][arch] = 1
+        Upload.pkg.changes["adv id"] = advisory
+        Upload.dump_vars(os.getcwd())
+
+def yes_no(prompt):
+    if Options["Automatic"]: return True
+    while 1:
+        answer = utils.our_raw_input(prompt + " ").lower()
+        if answer in "yn":
+            return answer == "y"
+        print "Invalid answer; please try again."
+
+def do_upload():
+    if Options["No-Upload"]:
+        print "Not uploading as requested"
+    elif Options["Foreground-Upload"]:
+        actually_upload(changes)
+    else:
+        child = os.fork()
+        if child == 0:
+            actually_upload(changes)
+            os._exit(0)
+        print "Uploading in the background"
+
+def actually_upload(changes_files):
+    file_list = ""
+    suites = {}
+    component_mapping = {}
+    for component in Cnf.SubTree("Security-Install::ComponentMappings").List():
+        component_mapping[component] = Cnf["Security-Install::ComponentMappings::%s" % (component)]
+    uploads = {}; # uploads[uri] = file_list
+    changesfiles = {}; # changesfiles[uri] = file_list
+    package_list = {} # package_list[source_name][version]
+    changes_files.sort(utils.changes_compare)
+    for changes_file in changes_files:
+        changes_file = utils.validate_changes_file_arg(changes_file)
+        # Reset variables
+        components = {}
+        upload_uris = {}
+        file_list = []
+        Upload.init_vars()
+        # Parse the .dak file for the .changes file
+        Upload.pkg.changes_file = changes_file
+        Upload.update_vars()
+        files = Upload.pkg.files
+        changes = Upload.pkg.changes
+        dsc = Upload.pkg.dsc
+        # Build the file list for this .changes file
+        for file in files.keys():
+            poolname = os.path.join(Cnf["Dir::Root"], Cnf["Dir::PoolRoot"],
+                                    utils.poolify(changes["source"], files[file]["component"]),
+                                    file)
+            file_list.append(poolname)
+            orig_component = files[file].get("original component", files[file]["component"])
+            components[orig_component] = ""
+        # Determine the upload uri for this .changes file
+        for component in components.keys():
+            upload_uri = component_mapping.get(component)
+            if upload_uri:
+                upload_uris[upload_uri] = ""
+        num_upload_uris = len(upload_uris.keys())
+        if num_upload_uris == 0:
+            utils.fubar("%s: No valid upload URI found from components (%s)."
+                        % (changes_file, ", ".join(components.keys())))
+        elif num_upload_uris > 1:
+            utils.fubar("%s: more than one upload URI (%s) from components (%s)."
+                        % (changes_file, ", ".join(upload_uris.keys()),
+                           ", ".join(components.keys())))
+        upload_uri = upload_uris.keys()[0]
+        # Update the file list for the upload uri
+        if not uploads.has_key(upload_uri):
+            uploads[upload_uri] = []
+        uploads[upload_uri].extend(file_list)
+        # Update the changes list for the upload uri
+        if not changesfiles.has_key(upload_uri):
+            changesfiles[upload_uri] = []
+        changesfiles[upload_uri].append(changes_file)
+        # Remember the suites and source name/version
+        for suite in changes["distribution"].keys():
+            suites[suite] = ""
+        # Remember the source name and version
+        if changes["architecture"].has_key("source") and \
+           changes["distribution"].has_key("testing"):
+            if not package_list.has_key(dsc["source"]):
+                package_list[dsc["source"]] = {}
+            package_list[dsc["source"]][dsc["version"]] = ""
+
+    for uri in uploads.keys():
+        uploads[uri].extend(changesfiles[uri])
+        (host, path) = uri.split(":")
+        #        file_list = " ".join(uploads[uri])
+        print "Moving files to UploadQueue"
+        for filename in uploads[uri]:
+            utils.copy(filename, Cnf["Dir::Upload"])
+            # .changes files have already been moved to queue/done by p-a
+            if not filename.endswith('.changes'):
+                remove_from_buildd(suites, filename)
+        #spawn("lftp -c 'open %s; cd %s; put %s'" % (host, path, file_list))
+
+    if not Options["No-Action"]:
+        filename = "%s/testing-processed" % (Cnf["Dir::Log"])
+        file = utils.open_file(filename, 'a')
+        for source in package_list.keys():
+            for version in package_list[source].keys():
+                file.write(" ".join([source, version])+'\n')
+        file.close()
+
+def remove_from_buildd(suites, filename):
+    """Check the buildd dir for each suite and remove the file if needed"""
+    builddbase = Cnf["Dir::QueueBuild"]
+    filebase = os.path.basename(filename)
+    for s in suites:
+        try:
+            os.unlink(os.path.join(builddbase, s, filebase))
+        except OSError, e:
+            utils.warn("Problem removing %s from buildd queue %s [%s]" % (filebase, s, str(e)))
+
+
+def generate_advisory(template):
+    global changes, advisory
+
+    adv_packages = []
+    updated_pkgs = {};  # updated_pkgs[distro][arch][file] = {path,md5,size}
+
+    for arg in changes:
+        arg = utils.validate_changes_file_arg(arg)
+        Upload.pkg.changes_file = arg
+        Upload.init_vars()
+        Upload.update_vars()
+
+        src = Upload.pkg.changes["source"]
+        src_ver = "%s (%s)" % (src, Upload.pkg.changes["version"])
+        if src_ver not in adv_packages:
+            adv_packages.append(src_ver)
+
+        suites = Upload.pkg.changes["distribution"].keys()
+        for suite in suites:
+            if not updated_pkgs.has_key(suite):
+                updated_pkgs[suite] = {}
+
+        files = Upload.pkg.files
+        for file in files.keys():
+            arch = files[file]["architecture"]
+            md5 = files[file]["md5sum"]
+            size = files[file]["size"]
+            poolname = Cnf["Dir::PoolRoot"] + \
+                utils.poolify(src, files[file]["component"])
+            if arch == "source" and file.endswith(".dsc"):
+                dscpoolname = poolname
+            for suite in suites:
+                if not updated_pkgs[suite].has_key(arch):
+                    updated_pkgs[suite][arch] = {}
+                updated_pkgs[suite][arch][file] = {
+                    "md5": md5, "size": size, "poolname": poolname }
+
+        dsc_files = Upload.pkg.dsc_files
+        for file in dsc_files.keys():
+            arch = "source"
+            if not dsc_files[file].has_key("files id"):
+                continue
+
+            # otherwise, it's already in the pool and needs to be
+            # listed specially
+            md5 = dsc_files[file]["md5sum"]
+            size = dsc_files[file]["size"]
+            for suite in suites:
+                if not updated_pkgs[suite].has_key(arch):
+                    updated_pkgs[suite][arch] = {}
+                updated_pkgs[suite][arch][file] = {
+                    "md5": md5, "size": size, "poolname": dscpoolname }
+
+    if os.environ.has_key("SUDO_UID"):
+        whoami = long(os.environ["SUDO_UID"])
+    else:
+        whoami = os.getuid()
+    whoamifull = pwd.getpwuid(whoami)
+    username = whoamifull[4].split(",")[0]
+
+    Subst = {
+        "__ADVISORY__": advisory,
+        "__WHOAMI__": username,
+        "__DATE__": time.strftime("%B %d, %Y", time.gmtime(time.time())),
+        "__PACKAGE__": ", ".join(adv_packages),
+        "__DAK_ADDRESS__": Cnf["Dinstall::MyEmailAddress"]
+        }
+
+    if Cnf.has_key("Dinstall::Bcc"):
+        Subst["__BCC__"] = "Bcc: %s" % (Cnf["Dinstall::Bcc"])
+
+    adv = ""
+    archive = Cnf["Archive::%s::PrimaryMirror" % (utils.where_am_i())]
+    for suite in updated_pkgs.keys():
+        ver = Cnf["Suite::%s::Version" % suite]
+        if ver != "": ver += " "
+        suite_header = "%s %s(%s)" % (Cnf["Dinstall::MyDistribution"],
+                                       ver, suite)
+        adv += "%s\n%s\n\n" % (suite_header, "-"*len(suite_header))
+
+        arches = Cnf.ValueList("Suite::%s::Architectures" % suite)
+        if "source" in arches:
+            arches.remove("source")
+        if "all" in arches:
+            arches.remove("all")
+        arches.sort()
+
+        adv += "%s updates are available for %s.\n\n" % (
+                suite.capitalize(), utils.join_with_commas_and(arches))
+
+        for a in ["source", "all"] + arches:
+            if not updated_pkgs[suite].has_key(a):
+                continue
+
+            if a == "source":
+                adv += "Source archives:\n\n"
+            elif a == "all":
+                adv += "Architecture independent packages:\n\n"
+            else:
+                adv += "%s architecture (%s)\n\n" % (a,
+                        Cnf["Architectures::%s" % a])
+
+            for file in updated_pkgs[suite][a].keys():
+                adv += "  http://%s/%s%s\n" % (
+                                archive, updated_pkgs[suite][a][file]["poolname"], file)
+                adv += "    Size/MD5 checksum: %8s %s\n" % (
+                        updated_pkgs[suite][a][file]["size"],
+                        updated_pkgs[suite][a][file]["md5"])
+            adv += "\n"
+    adv = adv.rstrip()
+
+    Subst["__ADVISORY_TEXT__"] = adv
+
+    adv = utils.TemplateSubst(Subst, template)
+    return adv
+
+def spawn(command):
+    if not re_taint_free.match(command):
+        utils.fubar("Invalid character in \"%s\"." % (command))
+
+    if Options["No-Action"]:
+        print "[%s]" % (command)
+    else:
+        (result, output) = commands.getstatusoutput(command)
+        if (result != 0):
+            utils.fubar("Invocation of '%s' failed:\n%s\n" % (command, output), result)
+
+
+##################### ! ! ! N O T E ! ! !  #####################
+#
+# These functions will be reinvoked by semi-priveleged users, be careful not
+# to invoke external programs that will escalate privileges, etc.
+#
+##################### ! ! ! N O T E ! ! !  #####################
+
+def sudo(arg, fn, exit):
+    if Options["Sudo"]:
+        if advisory == None:
+            utils.fubar("Must set advisory name")
+        os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H",
+                  "/usr/local/bin/dak", "new-security-install", "-"+arg, "--", advisory)
+    else:
+        fn()
+    if exit:
+        quit()
+
+def do_Approve(): sudo("A", _do_Approve, True)
+def _do_Approve():
+    # 1. dump advisory in drafts
+    draft = "/org/security.debian.org/advisories/drafts/%s" % (advisory)
+    print "Advisory in %s" % (draft)
+    if not Options["No-Action"]:
+        adv_file = "./advisory.%s" % (advisory)
+        if not os.path.exists(adv_file):
+            adv_file = Cnf["Dir::Templates"]+"/security-install.advisory"
+        adv_fd = os.open(draft, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0664)
+        os.write(adv_fd, generate_advisory(adv_file))
+        os.close(adv_fd)
+        adv_fd = None
+
+    # 2. run dak process-accepted on changes
+    print "Accepting packages..."
+    spawn("dak process-accepted -pa %s" % (" ".join(changes)))
+
+    # 3. run dak make-suite-file-list / apt-ftparchve / dak generate-releases
+    print "Updating file lists for apt-ftparchive..."
+    spawn("dak make-suite-file-list")
+    print "Updating Packages and Sources files..."
+    spawn("apt-ftparchive generate %s" % (utils.which_apt_conf_file()))
+    print "Updating Release files..."
+    spawn("dak generate-releases")
+    print "Triggering security mirrors..."
+    spawn("sudo -u archvsync -H /home/archvsync/signal_security")
+
+    # 4. chdir to done - do upload
+    if not Options["No-Action"]:
+        os.chdir(Cnf["Dir::Queue::Done"])
+    do_upload()
+
+def do_Disembargo(): sudo("D", _do_Disembargo, True)
+def _do_Disembargo():
+    if os.getcwd() != Cnf["Dir::Queue::Embargoed"].rstrip("/"):
+        utils.fubar("Can only disembargo from %s" % Cnf["Dir::Queue::Embargoed"])
+
+    dest = Cnf["Dir::Queue::Unembargoed"]
+    emb_q = database.get_or_set_queue_id("embargoed")
+    une_q = database.get_or_set_queue_id("unembargoed")
+
+    for c in changes:
+        print "Disembargoing %s" % (c)
+
+        Upload.init_vars()
+        Upload.pkg.changes_file = c
+        Upload.update_vars()
+
+        if "source" in Upload.pkg.changes["architecture"].keys():
+            print "Adding %s %s to disembargo table" % (Upload.pkg.changes["source"], Upload.pkg.changes["version"])
+            Upload.projectB.query("INSERT INTO disembargo (package, version) VALUES ('%s', '%s')" % (Upload.pkg.changes["source"], Upload.pkg.changes["version"]))
+
+        files = {}
+        for suite in Upload.pkg.changes["distribution"].keys():
+            if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
+                continue
+            dest_dir = Cnf["Dir::QueueBuild"]
+            if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+                dest_dir = os.path.join(dest_dir, suite)
+            for file in Upload.pkg.files.keys():
+                files[os.path.join(dest_dir, file)] = 1
+
+        files = files.keys()
+        Upload.projectB.query("BEGIN WORK")
+        for f in files:
+            Upload.projectB.query("UPDATE queue_build SET queue = %s WHERE filename = '%s' AND queue = %s" % (une_q, f, emb_q))
+        Upload.projectB.query("COMMIT WORK")
+
+        for file in Upload.pkg.files.keys():
+            utils.copy(file, os.path.join(dest, file))
+            os.unlink(file)
+
+    for c in changes:
+        utils.copy(c, os.path.join(dest, c))
+        os.unlink(c)
+        k = c[:-8] + ".dak"
+        utils.copy(k, os.path.join(dest, k))
+        os.unlink(k)
+
+def do_Reject(): sudo("R", _do_Reject, True)
+def _do_Reject():
+    global changes
+    for c in changes:
+        print "Rejecting %s..." % (c)
+        Upload.init_vars()
+        Upload.pkg.changes_file = c
+        Upload.update_vars()
+        files = {}
+        for suite in Upload.pkg.changes["distribution"].keys():
+            if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
+                continue
+            dest_dir = Cnf["Dir::QueueBuild"]
+            if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+                dest_dir = os.path.join(dest_dir, suite)
+            for file in Upload.pkg.files.keys():
+                files[os.path.join(dest_dir, file)] = 1
+
+        files = files.keys()
+
+        aborted = Upload.do_reject()
+        if not aborted:
+            os.unlink(c[:-8]+".dak")
+            for f in files:
+                Upload.projectB.query(
+                    "DELETE FROM queue_build WHERE filename = '%s'" % (f))
+                os.unlink(f)
+
+    print "Updating buildd information..."
+    spawn("/org/security.debian.org/dak/config/debian-security/cron.buildd")
+
+    adv_file = "./advisory.%s" % (advisory)
+    if os.path.exists(adv_file):
+        os.unlink(adv_file)
+
+def do_DropAdvisory():
+    for c in changes:
+        Upload.init_vars()
+        Upload.pkg.changes_file = c
+        Upload.update_vars()
+        del Upload.pkg.changes["adv id"]
+        Upload.dump_vars(os.getcwd())
+    quit()
+
+def do_Edit():
+    adv_file = "./advisory.%s" % (advisory)
+    if not os.path.exists(adv_file):
+        utils.copy(Cnf["Dir::Templates"]+"/security-install.advisory", adv_file)
+    editor = os.environ.get("EDITOR", "vi")
+    result = os.system("%s %s" % (editor, adv_file))
+    if result != 0:
+        utils.fubar("%s invocation failed for %s." % (editor, adv_file))
+
+def do_Show():
+    adv_file = "./advisory.%s" % (advisory)
+    if not os.path.exists(adv_file):
+        adv_file = Cnf["Dir::Templates"]+"/security-install.advisory"
+    print "====\n%s\n====" % (generate_advisory(adv_file))
+
+def do_Quit():
+    quit()
+
+def main():
+    global changes
+
+    args = init()
+    extras = load_args(args)
+    if advisory:
+        load_adv_changes()
+    if extras:
+        if not advisory:
+            changes = extras
+        else:
+            if srcverarches == {}:
+                if not yes_no("Create new advisory %s?" % (advisory)):
+                    print "Not doing anything, then"
+                    quit()
+            else:
+                advisory_info()
+                doextras = []
+                for c in extras:
+                    if yes_no("Add %s to %s?" % (c, advisory)):
+                        doextras.append(c)
+                extras = doextras
+            add_changes(extras)
+
+    if not advisory:
+        utils.fubar("Must specify an advisory id")
+
+    if not changes:
+        utils.fubar("No changes specified")
+
+    if Options["Approve"]:
+        advisory_info()
+        do_Approve()
+    elif Options["Reject"]:
+        advisory_info()
+        do_Reject()
+    elif Options["Disembargo"]:
+        advisory_info()
+        do_Disembargo()
+    elif Options["Drop-Advisory"]:
+        advisory_info()
+        do_DropAdvisory()
+    else:
+        while 1:
+            default = "Q"
+            opts = ["Approve", "Edit advisory"]
+            if os.path.exists("./advisory.%s" % advisory):
+                default = "A"
+            else:
+                default = "E"
+            if os.getcwd() == Cnf["Dir::Queue::Embargoed"].rstrip("/"):
+                opts.append("Disembargo")
+            opts += ["Show advisory", "Reject", "Quit"]
+
+            advisory_info()
+            what = prompt(opts, default)
+
+            if what == "Quit":
+                do_Quit()
+            elif what == "Approve":
+                do_Approve()
+            elif what == "Edit advisory":
+                do_Edit()
+            elif what == "Show advisory":
+                do_Show()
+            elif what == "Disembargo":
+                do_Disembargo()
+            elif what == "Reject":
+                do_Reject()
+            else:
+                utils.fubar("Impossible answer '%s', wtf?" % (what))
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
+
+################################################################################
diff --git a/dak/override.py b/dak/override.py
new file mode 100755 (executable)
index 0000000..ca3cb41
--- /dev/null
@@ -0,0 +1,278 @@
+#!/usr/bin/env python
+
+# Microscopic modification and query tool for overrides in projectb
+# Copyright (C) 2004, 2006  Daniel Silverstone <dsilvers@digital-scurf.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+################################################################################
+## So line up your soldiers and she'll shoot them all down
+## Coz Alisha Rules The World
+## You think you found a dream, then it shatters and it seems,
+## That Alisha Rules The World
+################################################################################
+
+import pg, sys
+import apt_pkg
+from daklib import logging
+from daklib import database
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+################################################################################
+
+# Shamelessly stolen from 'dak rm'. Should probably end up in utils.py
+def game_over():
+    answer = utils.our_raw_input("Continue (y/N)? ").lower()
+    if answer != "y":
+        print "Aborted."
+        sys.exit(1)
+
+
+def usage (exit_code=0):
+    print """Usage: dak override [OPTIONS] package [section] [priority]
+Make microchanges or microqueries of the binary overrides
+
+  -h, --help                 show this help and exit
+  -d, --done=BUG#            send priority/section change as closure to bug#
+  -n, --no-action            don't do anything
+  -s, --suite                specify the suite to use
+"""
+    sys.exit(exit_code)
+
+def main ():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Override::Options::Help"),
+                 ('d',"done","Override::Options::Done", "HasArg"),
+                 ('n',"no-action","Override::Options::No-Action"),
+                 ('s',"suite","Override::Options::Suite", "HasArg"),
+                 ]
+    for i in ["help", "no-action"]:
+        if not Cnf.has_key("Override::Options::%s" % (i)):
+            Cnf["Override::Options::%s" % (i)] = ""
+    if not Cnf.has_key("Override::Options::Suite"):
+        Cnf["Override::Options::Suite"] = "unstable"
+
+    arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Override::Options")
+
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    if not arguments:
+        utils.fubar("package name is a required argument.")
+
+    package = arguments.pop(0)
+    suite = Options["Suite"]
+    if arguments and len(arguments) > 2:
+        utils.fubar("Too many arguments")
+
+    if arguments and len(arguments) == 1:
+        # Determine if the argument is a priority or a section...
+        arg = arguments.pop()
+        q = projectB.query("""
+        SELECT ( SELECT COUNT(*) FROM section WHERE section=%s ) AS secs,
+               ( SELECT COUNT(*) FROM priority WHERE priority=%s ) AS prios
+               """ % ( pg._quote(arg,"str"), pg._quote(arg,"str")))
+        r = q.getresult()
+        if r[0][0] == 1:
+            arguments = (arg,".")
+        elif r[0][1] == 1:
+            arguments = (".",arg)
+        else:
+            utils.fubar("%s is not a valid section or priority" % (arg))
+
+    # Retrieve current section/priority...
+    oldsection, oldsourcesection, oldpriority = None, None, None
+    for type in ['source', 'binary']:
+        eqdsc = '!='
+        if type == 'source':
+            eqdsc = '='
+        q = projectB.query("""
+    SELECT priority.priority AS prio, section.section AS sect, override_type.type AS type
+      FROM override, priority, section, suite, override_type
+     WHERE override.priority = priority.id
+       AND override.type = override_type.id
+       AND override_type.type %s 'dsc'
+       AND override.section = section.id
+       AND override.package = %s
+       AND override.suite = suite.id
+       AND suite.suite_name = %s
+        """ % (eqdsc, pg._quote(package,"str"), pg._quote(suite,"str")))
+
+        if q.ntuples() == 0:
+            continue
+        if q.ntuples() > 1:
+            utils.fubar("%s is ambiguous. Matches %d packages" % (package,q.ntuples()))
+
+        r = q.getresult()
+        if type == 'binary':
+            oldsection = r[0][1]
+            oldpriority = r[0][0]
+        else:
+            oldsourcesection = r[0][1]
+
+    if not oldpriority and not oldsourcesection:
+        utils.fubar("Unable to find package %s" % (package))
+    if oldsection and oldsourcesection and oldsection != oldsourcesection:
+        # When setting overrides, both source & binary will become the same section
+        utils.warn("Source is in section '%s' instead of '%s'" % (oldsourcesection, oldsection))
+    if not oldsection:
+        oldsection = oldsourcesection
+
+    if not arguments:
+        if oldpriority:
+            print "%s is in section '%s' at priority '%s'" % (
+                package,oldsection,oldpriority)
+        elif oldsourcesection:
+            # no use printing this line if also binary
+            print "%s is in section '%s'" % (
+                package,oldsourcesection)
+        sys.exit(0)
+
+    # At this point, we have a new section and priority... check they're valid...
+    newsection, newpriority = arguments
+
+    if newsection == ".":
+        newsection = oldsection
+    if newpriority == ".":
+        newpriority = oldpriority
+
+    q = projectB.query("SELECT id FROM section WHERE section=%s" % (
+        pg._quote(newsection,"str")))
+
+    if q.ntuples() == 0:
+        utils.fubar("Supplied section %s is invalid" % (newsection))
+    newsecid = q.getresult()[0][0]
+
+    q = projectB.query("SELECT id FROM priority WHERE priority=%s" % (
+        pg._quote(newpriority,"str")))
+
+    if q.ntuples() == 0:
+        utils.fubar("Supplied priority %s is invalid" % (newpriority))
+    newprioid = q.getresult()[0][0]
+
+    if newpriority == oldpriority and newsection == oldsection:
+        print "I: Doing nothing"
+        sys.exit(0)
+
+    if newpriority and not oldpriority:
+        utils.fubar("Trying to set priority of a source-only package")
+
+    # If we're in no-action mode
+    if Options["No-Action"]:
+        if newpriority != oldpriority:
+            print "I: Would change priority from %s to %s" % (oldpriority,newpriority)
+        if newsection != oldsection:
+            print "I: Would change section from %s to %s" % (oldsection,newsection)
+        if Options.has_key("Done"):
+            print "I: Would also close bug(s): %s" % (Options["Done"])
+
+        sys.exit(0)
+
+    if newpriority != oldpriority:
+        print "I: Will change priority from %s to %s" % (oldpriority,newpriority)
+    if newsection != oldsection:
+        print "I: Will change section from %s to %s" % (oldsection,newsection)
+
+    if not Options.has_key("Done"):
+        pass
+        #utils.warn("No bugs to close have been specified. Noone will know you have done this.")
+    else:
+        print "I: Will close bug(s): %s" % (Options["Done"])
+
+    game_over()
+
+    Logger = logging.Logger(Cnf, "override")
+
+    projectB.query("BEGIN WORK")
+    # We're in "do it" mode, we have something to do... do it
+    if newpriority != oldpriority:
+        q = projectB.query("""
+        UPDATE override
+           SET priority=%d
+         WHERE package=%s
+           AND override.type != %d
+           AND suite = (SELECT id FROM suite WHERE suite_name=%s)""" % (
+            newprioid,
+            pg._quote(package,"str"), database.get_override_type_id("dsc"),
+            pg._quote(suite,"str") ))
+        Logger.log(["changed priority",package,oldpriority,newpriority])
+
+    if newsection != oldsection:
+        q = projectB.query("""
+        UPDATE override
+           SET section=%d
+         WHERE package=%s
+           AND suite = (SELECT id FROM suite WHERE suite_name=%s)""" % (
+            newsecid,
+            pg._quote(package,"str"),
+            pg._quote(suite,"str") ))
+        Logger.log(["changed section",package,oldsection,newsection])
+    projectB.query("COMMIT WORK")
+
+    if Options.has_key("Done"):
+        Subst = {}
+        Subst["__OVERRIDE_ADDRESS__"] = Cnf["Override::MyEmailAddress"]
+        Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]
+        bcc = []
+        if Cnf.Find("Dinstall::Bcc") != "":
+            bcc.append(Cnf["Dinstall::Bcc"])
+        if Cnf.Find("Override::Bcc") != "":
+            bcc.append(Cnf["Override::Bcc"])
+        if bcc:
+            Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
+        else:
+            Subst["__BCC__"] = "X-Filler: 42"
+        Subst["__CC__"] = "X-DAK: dak override\nX-Katie: alicia"
+        Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
+        Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
+        Subst["__WHOAMI__"] = utils.whoami()
+        Subst["__SOURCE__"] = package
+
+        summary = "Concerning package %s...\n" % (package)
+        summary += "Operating on the %s suite\n" % (suite)
+        if newpriority != oldpriority:
+            summary += "Changed priority from %s to %s\n" % (oldpriority,newpriority)
+        if newsection != oldsection:
+            summary += "Changed section from %s to %s\n" % (oldsection,newsection)
+        Subst["__SUMMARY__"] = summary
+
+        for bug in utils.split_args(Options["Done"]):
+            Subst["__BUG_NUMBER__"] = bug
+            mail_message = utils.TemplateSubst(
+                Subst,Cnf["Dir::Templates"]+"/override.bug-close")
+            utils.send_mail(mail_message)
+            Logger.log(["closed bug",bug])
+
+    Logger.close()
+
+    print "Done"
+
+#################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/poolize.py b/dak/poolize.py
new file mode 100644 (file)
index 0000000..5b7ffbf
--- /dev/null
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+
+# Poolify (move packages from "legacy" type locations to pool locations)
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# "Welcome to where time stands still,
+#  No one leaves and no one will."
+#   - Sanitarium - Metallica / Master of the puppets
+
+################################################################################
+
+import os, pg, re, stat, sys
+import apt_pkg, apt_inst
+import daklib.database
+import daklib.utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+re_isadeb = re.compile (r"(.+?)_(.+?)(_(.+))?\.u?deb$")
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak poolize [OPTIONS]
+Migrate packages from legacy locations into the pool.
+
+  -l, --limit=AMOUNT         only migrate AMOUNT Kb of packages
+  -n, --no-action            don't do anything
+  -v, --verbose              explain what is being done
+  -h, --help                 show this help and exit"""
+
+    sys.exit(exit_code)
+
+################################################################################
+
+# Q is a python-postgresql query result set and must have the
+# following four columns:
+#  o files.id (as 'files_id')
+#  o files.filename
+#  o location.path
+#  o component.name (as 'component')
+#
+# limit is a value in bytes or -1 for no limit (use with care!)
+# verbose and no_action are booleans
+
+def poolize (q, limit, verbose, no_action):
+    poolized_size = 0L
+    poolized_count = 0
+
+    # Parse -l/--limit argument
+    qd = q.dictresult()
+    for qid in qd:
+        legacy_filename = qid["path"]+qid["filename"]
+        size = os.stat(legacy_filename)[stat.ST_SIZE]
+        if (poolized_size + size) > limit and limit >= 0:
+            daklib.utils.warn("Hit %s limit." % (daklib.utils.size_type(limit)))
+            break
+        poolized_size += size
+        poolized_count += 1
+        base_filename = os.path.basename(legacy_filename)
+        destination_filename = base_filename
+        # Work out the source package name
+        if re_isadeb.match(base_filename):
+            control = apt_pkg.ParseSection(apt_inst.debExtractControl(daklib.utils.open_file(legacy_filename)))
+            package = control.Find("Package", "")
+            source = control.Find("Source", package)
+            if source.find("(") != -1:
+                m = daklib.utils.re_extract_src_version.match(source)
+                source = m.group(1)
+            # If it's a binary, we need to also rename the file to include the architecture
+            version = control.Find("Version", "")
+            architecture = control.Find("Architecture", "")
+            if package == "" or version == "" or architecture == "":
+                daklib.utils.fubar("%s: couldn't determine required information to rename .deb file." % (legacy_filename))
+            version = daklib.utils.re_no_epoch.sub('', version)
+            destination_filename = "%s_%s_%s.deb" % (package, version, architecture)
+        else:
+            m = daklib.utils.re_issource.match(base_filename)
+            if m:
+                source = m.group(1)
+            else:
+                daklib.utils.fubar("expansion of source filename '%s' failed." % (legacy_filename))
+        # Work out the component name
+        component = qid["component"]
+        if component == "":
+            q = projectB.query("SELECT DISTINCT(c.name) FROM override o, component c WHERE o.package = '%s' AND o.component = c.id;" % (source))
+            ql = q.getresult()
+            if not ql:
+                daklib.utils.fubar("No override match for '%s' so I can't work out the component." % (source))
+            if len(ql) > 1:
+                daklib.utils.fubar("Multiple override matches for '%s' so I can't work out the component." % (source))
+            component = ql[0][0]
+        # Work out the new location
+        q = projectB.query("SELECT l.id FROM location l, component c WHERE c.name = '%s' AND c.id = l.component AND l.type = 'pool';" % (component))
+        ql = q.getresult()
+        if len(ql) != 1:
+            daklib.utils.fubar("couldn't determine location ID for '%s'. [query returned %d matches, not 1 as expected]" % (source, len(ql)))
+        location_id = ql[0][0]
+        # First move the files to the new location
+        pool_location = daklib.utils.poolify (source, component)
+        pool_filename = pool_location + destination_filename
+        destination = Cnf["Dir::Pool"] + pool_location + destination_filename
+        if os.path.exists(destination):
+            daklib.utils.fubar("'%s' already exists in the pool; serious FUBARity." % (legacy_filename))
+        if verbose:
+            print "Moving: %s -> %s" % (legacy_filename, destination)
+        if not no_action:
+            daklib.utils.move(legacy_filename, destination)
+        # Then Update the DB's files table
+        if verbose:
+            print "SQL: UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, location_id, qid["files_id"])
+        if not no_action:
+            q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, location_id, qid["files_id"]))
+
+    sys.stderr.write("Poolized %s in %s files.\n" % (daklib.utils.size_type(poolized_size), poolized_count))
+
+################################################################################
+
+def main ():
+    global Cnf, projectB
+
+    Cnf = daklib.utils.get_conf()
+
+    for i in ["help", "limit", "no-action", "verbose" ]:
+        if not Cnf.has_key("Poolize::Options::%s" % (i)):
+            Cnf["Poolize::Options::%s" % (i)] = ""
+
+
+    Arguments = [('h',"help","Poolize::Options::Help"),
+                 ('l',"limit", "Poolize::Options::Limit", "HasArg"),
+                 ('n',"no-action","Poolize::Options::No-Action"),
+                 ('v',"verbose","Poolize::Options::Verbose")]
+
+    apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Poolize::Options")
+
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    daklib.database.init(Cnf, projectB)
+
+    if not Options["Limit"]:
+        limit = -1
+    else:
+        limit = int(Options["Limit"]) * 1024
+
+    # -n/--no-action implies -v/--verbose
+    if Options["No-Action"]:
+        Options["Verbose"] = "true"
+
+    # Sanity check the limit argument
+    if limit > 0 and limit < 1024:
+        daklib.utils.fubar("-l/--limit takes an argument with a value in kilobytes.")
+
+    # Grab a list of all files not already in the pool
+    q = projectB.query("""
+SELECT l.path, f.filename, f.id as files_id, c.name as component
+   FROM files f, location l, component c WHERE
+    NOT EXISTS (SELECT 1 FROM location l WHERE l.type = 'pool' AND f.location = l.id)
+    AND NOT (f.filename ~ '^potato') AND f.location = l.id AND l.component = c.id
+UNION SELECT l.path, f.filename, f.id as files_id, null as component
+   FROM files f, location l WHERE
+    NOT EXISTS (SELECT 1 FROM location l WHERE l.type = 'pool' AND f.location = l.id)
+    AND NOT (f.filename ~ '^potato') AND f.location = l.id AND NOT EXISTS
+     (SELECT 1 FROM location l WHERE l.component IS NOT NULL AND f.location = l.id);""")
+
+    poolize(q, limit, Options["Verbose"], Options["No-Action"])
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/process_accepted.py b/dak/process_accepted.py
new file mode 100755 (executable)
index 0000000..6013b18
--- /dev/null
@@ -0,0 +1,662 @@
+#!/usr/bin/env python
+
+# Installs Debian packages from queue/accepted into the pool
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+###############################################################################
+
+#    Cartman: "I'm trying to make the best of a bad situation, I don't
+#              need to hear crap from a bunch of hippy freaks living in
+#              denial.  Screw you guys, I'm going home."
+#
+#    Kyle: "But Cartman, we're trying to..."
+#
+#    Cartman: "uhh.. screw you guys... home."
+
+###############################################################################
+
+import errno, fcntl, os, sys, time, re
+import apt_pkg
+from daklib import database
+from daklib import logging
+from daklib import queue
+from daklib import utils
+from daklib.dak_exceptions import *
+
+###############################################################################
+
+Cnf = None
+Options = None
+Logger = None
+Urgency_Logger = None
+projectB = None
+Upload = None
+pkg = None
+
+reject_message = ""
+changes = None
+dsc = None
+dsc_files = None
+files = None
+Subst = None
+
+install_count = 0
+install_bytes = 0.0
+
+installing_to_stable = 0
+
+###############################################################################
+
+# FIXME: this should go away to some Debian specific file
+# FIXME: should die if file already exists
+
+class Urgency_Log:
+    "Urgency Logger object"
+    def __init__ (self, Cnf):
+        "Initialize a new Urgency Logger object"
+        self.Cnf = Cnf
+        self.timestamp = time.strftime("%Y%m%d%H%M%S")
+        # Create the log directory if it doesn't exist
+        self.log_dir = Cnf["Dir::UrgencyLog"]
+        if not os.path.exists(self.log_dir) or not os.access(self.log_dir, os.W_OK):
+            utils.warn("UrgencyLog directory %s does not exist or is not writeable, using /srv/ftp.debian.org/tmp/ instead" % (self.log_dir))
+            self.log_dir = '/srv/ftp.debian.org/tmp/'
+        # Open the logfile
+        self.log_filename = "%s/.install-urgencies-%s.new" % (self.log_dir, self.timestamp)
+        self.log_file = utils.open_file(self.log_filename, 'w')
+        self.writes = 0
+
+    def log (self, source, version, urgency):
+        "Log an event"
+        self.log_file.write(" ".join([source, version, urgency])+'\n')
+        self.log_file.flush()
+        self.writes += 1
+
+    def close (self):
+        "Close a Logger object"
+        self.log_file.flush()
+        self.log_file.close()
+        if self.writes:
+            new_filename = "%s/install-urgencies-%s" % (self.log_dir, self.timestamp)
+            utils.move(self.log_filename, new_filename)
+        else:
+            os.unlink(self.log_filename)
+
+###############################################################################
+
+def reject (str, prefix="Rejected: "):
+    global reject_message
+    if str:
+        reject_message += prefix + str + "\n"
+
+# Recheck anything that relies on the database; since that's not
+# frozen between accept and our run time.
+
+def check():
+    propogate={}
+    nopropogate={}
+    for file in files.keys():
+        # The .orig.tar.gz can disappear out from under us is it's a
+        # duplicate of one in the archive.
+        if not files.has_key(file):
+            continue
+        # Check that the source still exists
+        if files[file]["type"] == "deb":
+            source_version = files[file]["source version"]
+            source_package = files[file]["source package"]
+            if not changes["architecture"].has_key("source") \
+               and not Upload.source_exists(source_package, source_version,  changes["distribution"].keys()):
+                reject("no source found for %s %s (%s)." % (source_package, source_version, file))
+
+        # Version and file overwrite checks
+        if not installing_to_stable:
+            if files[file]["type"] == "deb":
+                reject(Upload.check_binary_against_db(file), "")
+            elif files[file]["type"] == "dsc":
+                reject(Upload.check_source_against_db(file), "")
+                (reject_msg, is_in_incoming) = Upload.check_dsc_against_db(file)
+                reject(reject_msg, "")
+
+        # propogate in the case it is in the override tables:
+        if changes.has_key("propdistribution"):
+            for suite in changes["propdistribution"].keys():
+                if Upload.in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
+                    propogate[suite] = 1
+                else:
+                    nopropogate[suite] = 1
+
+    for suite in propogate.keys():
+        if suite in nopropogate:
+            continue
+        changes["distribution"][suite] = 1
+
+    for file in files.keys():
+        # Check the package is still in the override tables
+        for suite in changes["distribution"].keys():
+            if not Upload.in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
+                reject("%s is NEW for %s." % (file, suite))
+
+###############################################################################
+
+def init():
+    global Cnf, Options, Upload, projectB, changes, dsc, dsc_files, files, pkg, Subst
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
+                 ('h',"help","Dinstall::Options::Help"),
+                 ('n',"no-action","Dinstall::Options::No-Action"),
+                 ('p',"no-lock", "Dinstall::Options::No-Lock"),
+                 ('s',"no-mail", "Dinstall::Options::No-Mail")]
+
+    for i in ["automatic", "help", "no-action", "no-lock", "no-mail", "version"]:
+        if not Cnf.has_key("Dinstall::Options::%s" % (i)):
+            Cnf["Dinstall::Options::%s" % (i)] = ""
+
+    changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Dinstall::Options")
+
+    if Options["Help"]:
+        usage()
+
+    Upload = queue.Upload(Cnf)
+    projectB = Upload.projectB
+
+    changes = Upload.pkg.changes
+    dsc = Upload.pkg.dsc
+    dsc_files = Upload.pkg.dsc_files
+    files = Upload.pkg.files
+    pkg = Upload.pkg
+    Subst = Upload.Subst
+
+    return changes_files
+
+###############################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak process-accepted [OPTION]... [CHANGES]...
+  -a, --automatic           automatic run
+  -h, --help                show this help and exit.
+  -n, --no-action           don't do anything
+  -p, --no-lock             don't check lockfile !! for cron.daily only !!
+  -s, --no-mail             don't send any mail
+  -V, --version             display the version number and exit"""
+    sys.exit(exit_code)
+
+###############################################################################
+
+def action ():
+    (summary, short_summary) = Upload.build_summaries()
+
+    (prompt, answer) = ("", "XXX")
+    if Options["No-Action"] or Options["Automatic"]:
+        answer = 'S'
+
+    if reject_message.find("Rejected") != -1:
+        print "REJECT\n" + reject_message,
+        prompt = "[R]eject, Skip, Quit ?"
+        if Options["Automatic"]:
+            answer = 'R'
+    else:
+        print "INSTALL to " + ", ".join(changes["distribution"].keys())
+        print reject_message + summary,
+        prompt = "[I]nstall, Skip, Quit ?"
+        if Options["Automatic"]:
+            answer = 'I'
+
+    while prompt.find(answer) == -1:
+        answer = utils.our_raw_input(prompt)
+        m = queue.re_default_answer.match(prompt)
+        if answer == "":
+            answer = m.group(1)
+        answer = answer[:1].upper()
+
+    if answer == 'R':
+        do_reject ()
+    elif answer == 'I':
+        if not installing_to_stable:
+            install()
+        else:
+            stable_install(summary, short_summary)
+    elif answer == 'Q':
+        sys.exit(0)
+
+###############################################################################
+
+# Our reject is not really a reject, but an unaccept, but since a) the
+# code for that is non-trivial (reopen bugs, unannounce etc.), b) this
+# should be exteremly rare, for now we'll go with whining at our admin
+# folks...
+
+def do_reject ():
+    Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
+    Subst["__REJECT_MESSAGE__"] = reject_message
+    Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
+    reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-accepted.unaccept")
+
+    # Write the rejection email out as the <foo>.reason file
+    reason_filename = os.path.basename(pkg.changes_file[:-8]) + ".reason"
+    reject_filename = Cnf["Dir::Queue::Reject"] + '/' + reason_filename
+    # If we fail here someone is probably trying to exploit the race
+    # so let's just raise an exception ...
+    if os.path.exists(reject_filename):
+        os.unlink(reject_filename)
+    fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
+    os.write(fd, reject_mail_message)
+    os.close(fd)
+
+    utils.send_mail(reject_mail_message)
+    Logger.log(["unaccepted", pkg.changes_file])
+
+###############################################################################
+
+def install ():
+    global install_count, install_bytes
+
+    print "Installing."
+
+    Logger.log(["installing changes",pkg.changes_file])
+
+    # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
+    projectB.query("BEGIN WORK")
+
+    # Ensure that we have all the hashes we need below.
+    rejmsg = utils.ensure_hashes(changes, dsc, files, dsc_files)
+    if len(rejmsg) > 0:
+        # There were errors.  Print them and SKIP the changes.
+        for msg in rejmsg:
+            utils.warn(msg)
+        return
+
+    # Add the .dsc file to the DB
+    for file in files.keys():
+        if files[file]["type"] == "dsc":
+            package = dsc["source"]
+            version = dsc["version"]  # NB: not files[file]["version"], that has no epoch
+            maintainer = dsc["maintainer"]
+            maintainer = maintainer.replace("'", "\\'")
+            maintainer_id = database.get_or_set_maintainer_id(maintainer)
+            changedby = changes["changed-by"]
+            changedby = changedby.replace("'", "\\'")
+            changedby_id = database.get_or_set_maintainer_id(changedby)
+            fingerprint_id = database.get_or_set_fingerprint_id(dsc["fingerprint"])
+            install_date = time.strftime("%Y-%m-%d")
+            filename = files[file]["pool name"] + file
+            dsc_component = files[file]["component"]
+            dsc_location_id = files[file]["location id"]
+            if not files[file].has_key("files id") or not files[file]["files id"]:
+                files[file]["files id"] = database.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["sha1sum"], files[file]["sha256sum"], dsc_location_id)
+            projectB.query("INSERT INTO source (source, version, maintainer, changedby, file, install_date, sig_fpr) VALUES ('%s', '%s', %d, %d, %d, '%s', %s)"
+                           % (package, version, maintainer_id, changedby_id, files[file]["files id"], install_date, fingerprint_id))
+
+            for suite in changes["distribution"].keys():
+                suite_id = database.get_suite_id(suite)
+                projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
+
+            # Add the source files to the DB (files and dsc_files)
+            projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]))
+            for dsc_file in dsc_files.keys():
+                filename = files[file]["pool name"] + dsc_file
+                # If the .orig.tar.gz is already in the pool, it's
+                # files id is stored in dsc_files by check_dsc().
+                files_id = dsc_files[dsc_file].get("files id", None)
+                if files_id == None:
+                    files_id = database.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id)
+                # FIXME: needs to check for -1/-2 and or handle exception
+                if files_id == None:
+                    files_id = database.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], files[dsc_file]["sha1sum"], files[dsc_file]["sha256sum"], dsc_location_id)
+                projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id))
+
+            # Add the src_uploaders to the DB
+            if dsc.get("dm-upload-allowed", "no") == "yes":
+                uploader_ids = [maintainer_id]
+                if dsc.has_key("uploaders"):
+                    for u in dsc["uploaders"].split(","):
+                        u = u.replace("'", "\\'")
+                        u = u.strip()
+                        uploader_ids.append(
+                            database.get_or_set_maintainer_id(u))
+                added_ids = {}
+                for u in uploader_ids:
+                    if added_ids.has_key(u):
+                        utils.warn("Already saw uploader %s for source %s" % (u, package))
+                        continue
+                    added_ids[u]=1
+                    projectB.query("INSERT INTO src_uploaders (source, maintainer) VALUES (currval('source_id_seq'), %d)" % (u))
+
+
+    # Add the .deb files to the DB
+    for file in files.keys():
+        if files[file]["type"] == "deb":
+            package = files[file]["package"]
+            version = files[file]["version"]
+            maintainer = files[file]["maintainer"]
+            maintainer = maintainer.replace("'", "\\'")
+            maintainer_id = database.get_or_set_maintainer_id(maintainer)
+            fingerprint_id = database.get_or_set_fingerprint_id(changes["fingerprint"])
+            architecture = files[file]["architecture"]
+            architecture_id = database.get_architecture_id (architecture)
+            type = files[file]["dbtype"]
+            source = files[file]["source package"]
+            source_version = files[file]["source version"]
+            filename = files[file]["pool name"] + file
+            if not files[file].has_key("location id") or not files[file]["location id"]:
+                files[file]["location id"] = database.get_location_id(Cnf["Dir::Pool"],files[file]["component"],utils.where_am_i())
+            if not files[file].has_key("files id") or not files[file]["files id"]:
+                files[file]["files id"] = database.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["sha1sum"], files[file]["sha256sum"], files[file]["location id"])
+            source_id = database.get_source_id (source, source_version)
+            if source_id:
+                projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type, sig_fpr) VALUES ('%s', '%s', %d, %d, %d, %d, '%s', %d)"
+                               % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type, fingerprint_id))
+            else:
+                raise NoSourceFieldError, "Unable to find a source id for %s (%s), %s, file %s, type %s, signed by %s" % (package, version, architecture, file, type, sig_fpr)
+            for suite in changes["distribution"].keys():
+                suite_id = database.get_suite_id(suite)
+                projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id))
+
+    # If the .orig.tar.gz is in a legacy directory we need to poolify
+    # it, so that apt-get source (and anything else that goes by the
+    # "Directory:" field in the Sources.gz file) works.
+    orig_tar_id = Upload.pkg.orig_tar_id
+    orig_tar_location = Upload.pkg.orig_tar_location
+    legacy_source_untouchable = Upload.pkg.legacy_source_untouchable
+    if orig_tar_id and orig_tar_location == "legacy":
+        q = projectB.query("SELECT DISTINCT ON (f.id) l.path, f.filename, f.id as files_id, df.source, df.id as dsc_files_id, f.size, f.md5sum FROM files f, dsc_files df, location l WHERE df.source IN (SELECT source FROM dsc_files WHERE file = %s) AND f.id = df.file AND l.id = f.location AND (l.type = 'legacy' OR l.type = 'legacy-mixed')" % (orig_tar_id))
+        qd = q.dictresult()
+        for qid in qd:
+            # Is this an old upload superseded by a newer -sa upload?  (See check_dsc() for details)
+            if legacy_source_untouchable.has_key(qid["files_id"]):
+                continue
+            # First move the files to the new location
+            legacy_filename = qid["path"] + qid["filename"]
+            pool_location = utils.poolify (changes["source"], files[file]["component"])
+            pool_filename = pool_location + os.path.basename(qid["filename"])
+            destination = Cnf["Dir::Pool"] + pool_location
+            utils.move(legacy_filename, destination)
+            # Then Update the DB's files table
+            q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]))
+
+    # If this is a sourceful diff only upload that is moving non-legacy
+    # cross-component we need to copy the .orig.tar.gz into the new
+    # component too for the same reasons as above.
+    #
+    if changes["architecture"].has_key("source") and orig_tar_id and \
+       orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
+        q = projectB.query("SELECT l.path, f.filename, f.size, f.md5sum, f.sha1sum, f.sha256sum FROM files f, location l WHERE f.id = %s AND f.location = l.id" % (orig_tar_id))
+        ql = q.getresult()[0]
+        old_filename = ql[0] + ql[1]
+        file_size = ql[2]
+        file_md5sum = ql[3]
+        file_sha1sum = ql[4]
+        file_sha256sum = ql[5]
+        new_filename = utils.poolify(changes["source"], dsc_component) + os.path.basename(old_filename)
+        new_files_id = database.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id)
+        if new_files_id == None:
+            utils.copy(old_filename, Cnf["Dir::Pool"] + new_filename)
+            new_files_id = database.set_files_id(new_filename, file_size, file_md5sum, file_sha1sum, file_sha256sum, dsc_location_id)
+            projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id))
+
+    # Install the files into the pool
+    for file in files.keys():
+        destination = Cnf["Dir::Pool"] + files[file]["pool name"] + file
+        utils.move(file, destination)
+        Logger.log(["installed", file, files[file]["type"], files[file]["size"], files[file]["architecture"]])
+        install_bytes += float(files[file]["size"])
+
+    # Copy the .changes file across for suite which need it.
+    copy_changes = {}
+    copy_dot_dak = {}
+    for suite in changes["distribution"].keys():
+        if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
+            copy_changes[Cnf["Suite::%s::CopyChanges" % (suite)]] = ""
+        # and the .dak file...
+        if Cnf.has_key("Suite::%s::CopyDotDak" % (suite)):
+            copy_dot_dak[Cnf["Suite::%s::CopyDotDak" % (suite)]] = ""
+    for dest in copy_changes.keys():
+        utils.copy(pkg.changes_file, Cnf["Dir::Root"] + dest)
+    for dest in copy_dot_dak.keys():
+        utils.copy(Upload.pkg.changes_file[:-8]+".dak", dest)
+
+    projectB.query("COMMIT WORK")
+
+    # Move the .changes into the 'done' directory
+    utils.move (pkg.changes_file,
+                os.path.join(Cnf["Dir::Queue::Done"], os.path.basename(pkg.changes_file)))
+
+    # Remove the .dak file
+    os.unlink(Upload.pkg.changes_file[:-8]+".dak")
+
+    if changes["architecture"].has_key("source") and Urgency_Logger:
+        Urgency_Logger.log(dsc["source"], dsc["version"], changes["urgency"])
+
+    # Undo the work done in queue.py(accept) to help auto-building
+    # from accepted.
+    projectB.query("BEGIN WORK")
+    for suite in changes["distribution"].keys():
+        if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
+            continue
+        now_date = time.strftime("%Y-%m-%d %H:%M")
+        suite_id = database.get_suite_id(suite)
+        dest_dir = Cnf["Dir::QueueBuild"]
+        if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+            dest_dir = os.path.join(dest_dir, suite)
+        for file in files.keys():
+            dest = os.path.join(dest_dir, file)
+            # Remove it from the list of packages for later processing by apt-ftparchive
+            projectB.query("UPDATE queue_build SET in_queue = 'f', last_used = '%s' WHERE filename = '%s' AND suite = %s" % (now_date, dest, suite_id))
+            if not Cnf.FindB("Dinstall::SecurityQueueBuild"):
+                # Update the symlink to point to the new location in the pool
+                pool_location = utils.poolify (changes["source"], files[file]["component"])
+                src = os.path.join(Cnf["Dir::Pool"], pool_location, os.path.basename(file))
+                if os.path.islink(dest):
+                    os.unlink(dest)
+                os.symlink(src, dest)
+        # Update last_used on any non-upload .orig.tar.gz symlink
+        if orig_tar_id:
+            # Determine the .orig.tar.gz file name
+            for dsc_file in dsc_files.keys():
+                if dsc_file.endswith(".orig.tar.gz"):
+                    orig_tar_gz = os.path.join(dest_dir, dsc_file)
+            # Remove it from the list of packages for later processing by apt-ftparchive
+            projectB.query("UPDATE queue_build SET in_queue = 'f', last_used = '%s' WHERE filename = '%s' AND suite = %s" % (now_date, orig_tar_gz, suite_id))
+    projectB.query("COMMIT WORK")
+
+    # Finally...
+    install_count += 1
+
+################################################################################
+
+def stable_install (summary, short_summary):
+    global install_count
+
+    print "Installing to stable."
+
+    # Begin a transaction; if we bomb out anywhere between here and
+    # the COMMIT WORK below, the DB won't be changed.
+    projectB.query("BEGIN WORK")
+
+    # Add the source to stable (and remove it from proposed-updates)
+    for file in files.keys():
+        if files[file]["type"] == "dsc":
+            package = dsc["source"]
+            version = dsc["version"];  # NB: not files[file]["version"], that has no epoch
+            q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
+            ql = q.getresult()
+            if not ql:
+                utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s) in source table." % (package, version))
+            source_id = ql[0][0]
+            suite_id = database.get_suite_id('proposed-updates')
+            projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id))
+            suite_id = database.get_suite_id('stable')
+            projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id))
+
+    # Add the binaries to stable (and remove it/them from proposed-updates)
+    for file in files.keys():
+        if files[file]["type"] == "deb":
+            package = files[file]["package"]
+            version = files[file]["version"]
+            architecture = files[file]["architecture"]
+            q = projectB.query("SELECT b.id FROM binaries b, architecture a WHERE b.package = '%s' AND b.version = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all') AND b.architecture = a.id" % (package, version, architecture))
+            ql = q.getresult()
+            if not ql:
+                utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s for %s architecture) in binaries table." % (package, version, architecture))
+
+            binary_id = ql[0][0]
+            suite_id = database.get_suite_id('proposed-updates')
+            projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id))
+            suite_id = database.get_suite_id('stable')
+            projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id))
+
+    projectB.query("COMMIT WORK")
+
+    utils.move (pkg.changes_file, Cnf["Dir::Morgue"] + '/process-accepted/' + os.path.basename(pkg.changes_file))
+
+    ## Update the Stable ChangeLog file
+    new_changelog_filename = Cnf["Dir::Root"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog"
+    changelog_filename = Cnf["Dir::Root"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog"
+    if os.path.exists(new_changelog_filename):
+        os.unlink (new_changelog_filename)
+
+    new_changelog = utils.open_file(new_changelog_filename, 'w')
+    for file in files.keys():
+        if files[file]["type"] == "deb":
+            new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file))
+        elif utils.re_issource.match(file):
+            new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file))
+        else:
+            new_changelog.write("%s\n" % (file))
+    chop_changes = queue.re_fdnic.sub("\n", changes["changes"])
+    new_changelog.write(chop_changes + '\n\n')
+    if os.access(changelog_filename, os.R_OK) != 0:
+        changelog = utils.open_file(changelog_filename)
+        new_changelog.write(changelog.read())
+    new_changelog.close()
+    if os.access(changelog_filename, os.R_OK) != 0:
+        os.unlink(changelog_filename)
+    utils.move(new_changelog_filename, changelog_filename)
+
+    install_count += 1
+
+    if not Options["No-Mail"] and changes["architecture"].has_key("source"):
+        Subst["__SUITE__"] = " into stable"
+        Subst["__SUMMARY__"] = summary
+        mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-accepted.install")
+        utils.send_mail(mail_message)
+        Upload.announce(short_summary, 1)
+
+    # Finally remove the .dak file
+    dot_dak_file = os.path.join(Cnf["Suite::Proposed-Updates::CopyDotDak"], os.path.basename(Upload.pkg.changes_file[:-8]+".dak"))
+    os.unlink(dot_dak_file)
+
+################################################################################
+
+def process_it (changes_file):
+    global reject_message
+
+    reject_message = ""
+
+    # Absolutize the filename to avoid the requirement of being in the
+    # same directory as the .changes file.
+    pkg.changes_file = os.path.abspath(changes_file)
+
+    # And since handling of installs to stable munges with the CWD
+    # save and restore it.
+    pkg.directory = os.getcwd()
+
+    if installing_to_stable:
+        old = Upload.pkg.changes_file
+        Upload.pkg.changes_file = os.path.basename(old)
+        os.chdir(Cnf["Suite::Proposed-Updates::CopyDotDak"])
+
+    Upload.init_vars()
+    Upload.update_vars()
+    Upload.update_subst()
+
+    if installing_to_stable:
+        Upload.pkg.changes_file = old
+
+    check()
+    action()
+
+    # Restore CWD
+    os.chdir(pkg.directory)
+
+###############################################################################
+
+def main():
+    global projectB, Logger, Urgency_Logger, installing_to_stable
+
+    changes_files = init()
+
+    # -n/--dry-run invalidates some other options which would involve things happening
+    if Options["No-Action"]:
+        Options["Automatic"] = ""
+
+    # Check that we aren't going to clash with the daily cron job
+
+    if not Options["No-Action"] and os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::Root"])) and not Options["No-Lock"]:
+        utils.fubar("Archive maintenance in progress.  Try again later.")
+
+    # If running from within proposed-updates; assume an install to stable
+    if os.getcwd().find('proposed-updates') != -1:
+        installing_to_stable = 1
+
+    # Obtain lock if not in no-action mode and initialize the log
+    if not Options["No-Action"]:
+        lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR | os.O_CREAT)
+        try:
+            fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+        except IOError, e:
+            if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EAGAIN':
+                utils.fubar("Couldn't obtain lock; assuming another 'dak process-accepted' is already running.")
+            else:
+                raise
+        Logger = Upload.Logger = logging.Logger(Cnf, "process-accepted")
+        if not installing_to_stable and Cnf.get("Dir::UrgencyLog"):
+            Urgency_Logger = Urgency_Log(Cnf)
+
+    # Initialize the substitution template mapping global
+    bcc = "X-DAK: dak process-accepted\nX-Katie: $Revision: 1.18 $"
+    if Cnf.has_key("Dinstall::Bcc"):
+        Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
+    else:
+        Subst["__BCC__"] = bcc
+
+    # Sort the .changes files so that we process sourceful ones first
+    changes_files.sort(utils.changes_compare)
+
+    # Process the changes files
+    for changes_file in changes_files:
+        print "\n" + changes_file
+        process_it (changes_file)
+
+    if install_count:
+        sets = "set"
+        if install_count > 1:
+            sets = "sets"
+        sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))))
+        Logger.log(["total",install_count,install_bytes])
+
+    if not Options["No-Action"]:
+        Logger.close()
+        if Urgency_Logger:
+            Urgency_Logger.close()
+
+###############################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/process_new.py b/dak/process_new.py
new file mode 100755 (executable)
index 0000000..0b9ff92
--- /dev/null
@@ -0,0 +1,1053 @@
+#!/usr/bin/env python
+# vim:set et ts=4 sw=4:
+
+# Handles NEW and BYHAND packages
+# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# 23:12|<aj> I will not hush!
+# 23:12|<elmo> :>
+# 23:12|<aj> Where there is injustice in the world, I shall be there!
+# 23:13|<aj> I shall not be silenced!
+# 23:13|<aj> The world shall know!
+# 23:13|<aj> The world *must* know!
+# 23:13|<elmo> oh dear, he's gone back to powerpuff girls... ;-)
+# 23:13|<aj> yay powerpuff girls!!
+# 23:13|<aj> buttercup's my favourite, who's yours?
+# 23:14|<aj> you're backing away from the keyboard right now aren't you?
+# 23:14|<aj> *AREN'T YOU*?!
+# 23:15|<aj> I will not be treated like this.
+# 23:15|<aj> I shall have my revenge.
+# 23:15|<aj> I SHALL!!!
+
+################################################################################
+
+import copy, errno, os, readline, stat, sys, time
+import apt_pkg, apt_inst
+import examine_package
+from daklib import database
+from daklib import logging
+from daklib import queue
+from daklib import utils
+
+# Globals
+Cnf = None
+Options = None
+Upload = None
+projectB = None
+Logger = None
+
+Priorities = None
+Sections = None
+
+reject_message = ""
+
+################################################################################
+################################################################################
+################################################################################
+
+def reject (str, prefix="Rejected: "):
+    global reject_message
+    if str:
+        reject_message += prefix + str + "\n"
+
+def recheck():
+    global reject_message
+    files = Upload.pkg.files
+    reject_message = ""
+
+    for f in files.keys():
+        # The .orig.tar.gz can disappear out from under us is it's a
+        # duplicate of one in the archive.
+        if not files.has_key(f):
+            continue
+        # Check that the source still exists
+        if files[f]["type"] == "deb":
+            source_version = files[f]["source version"]
+            source_package = files[f]["source package"]
+            if not Upload.pkg.changes["architecture"].has_key("source") \
+               and not Upload.source_exists(source_package, source_version, Upload.pkg.changes["distribution"].keys()):
+                source_epochless_version = utils.re_no_epoch.sub('', source_version)
+                dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
+                found = 0
+                for q in ["Accepted", "Embargoed", "Unembargoed"]:
+                    if Cnf.has_key("Dir::Queue::%s" % (q)):
+                        if os.path.exists(Cnf["Dir::Queue::%s" % (q)] + '/' + dsc_filename):
+                            found = 1
+                if not found:
+                    reject("no source found for %s %s (%s)." % (source_package, source_version, f))
+
+        # Version and file overwrite checks
+        if files[f]["type"] == "deb":
+            reject(Upload.check_binary_against_db(f), "")
+        elif files[f]["type"] == "dsc":
+            reject(Upload.check_source_against_db(f), "")
+            (reject_msg, is_in_incoming) = Upload.check_dsc_against_db(f)
+            reject(reject_msg, "")
+
+    if reject_message.find("Rejected") != -1:
+        answer = "XXX"
+        if Options["No-Action"] or Options["Automatic"]:
+            answer = 'S'
+
+        print "REJECT\n" + reject_message,
+        prompt = "[R]eject, Skip, Quit ?"
+
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = queue.re_default_answer.match(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+
+        if answer == 'R':
+            Upload.do_reject(0, reject_message)
+            os.unlink(Upload.pkg.changes_file[:-8]+".dak")
+            return 0
+        elif answer == 'S':
+            return 0
+        elif answer == 'Q':
+            end()
+            sys.exit(0)
+
+    return 1
+
+################################################################################
+
+def indiv_sg_compare (a, b):
+    """Sort by source name, source, version, 'have source', and
+       finally by filename."""
+    # Sort by source version
+    q = apt_pkg.VersionCompare(a["version"], b["version"])
+    if q:
+        return -q
+
+    # Sort by 'have source'
+    a_has_source = a["architecture"].get("source")
+    b_has_source = b["architecture"].get("source")
+    if a_has_source and not b_has_source:
+        return -1
+    elif b_has_source and not a_has_source:
+        return 1
+
+    return cmp(a["filename"], b["filename"])
+
+############################################################
+
+def sg_compare (a, b):
+    a = a[1]
+    b = b[1]
+    """Sort by have note, source already in database and time of oldest upload."""
+    # Sort by have note
+    a_note_state = a["note_state"]
+    b_note_state = b["note_state"]
+    if a_note_state < b_note_state:
+        return -1
+    elif a_note_state > b_note_state:
+        return 1
+    # Sort by source already in database (descending)
+    source_in_database = cmp(a["source_in_database"], b["source_in_database"])
+    if source_in_database:
+        return -source_in_database
+
+    # Sort by time of oldest upload
+    return cmp(a["oldest"], b["oldest"])
+
+def sort_changes(changes_files):
+    """Sort into source groups, then sort each source group by version,
+    have source, filename.  Finally, sort the source groups by have
+    note, time of oldest upload of each source upload."""
+    if len(changes_files) == 1:
+        return changes_files
+
+    sorted_list = []
+    cache = {}
+    # Read in all the .changes files
+    for filename in changes_files:
+        try:
+            Upload.pkg.changes_file = filename
+            Upload.init_vars()
+            Upload.update_vars()
+            cache[filename] = copy.copy(Upload.pkg.changes)
+            cache[filename]["filename"] = filename
+        except:
+            sorted_list.append(filename)
+            break
+    # Divide the .changes into per-source groups
+    per_source = {}
+    for filename in cache.keys():
+        source = cache[filename]["source"]
+        if not per_source.has_key(source):
+            per_source[source] = {}
+            per_source[source]["list"] = []
+        per_source[source]["list"].append(cache[filename])
+    # Determine oldest time and have note status for each source group
+    for source in per_source.keys():
+        q = projectB.query("SELECT 1 FROM source WHERE source = '%s'" % source)
+        ql = q.getresult()
+        per_source[source]["source_in_database"] = len(ql)>0
+        source_list = per_source[source]["list"]
+        first = source_list[0]
+        oldest = os.stat(first["filename"])[stat.ST_MTIME]
+        have_note = 0
+        for d in per_source[source]["list"]:
+            mtime = os.stat(d["filename"])[stat.ST_MTIME]
+            if mtime < oldest:
+                oldest = mtime
+            have_note += (d.has_key("process-new note"))
+        per_source[source]["oldest"] = oldest
+        if not have_note:
+            per_source[source]["note_state"] = 0; # none
+        elif have_note < len(source_list):
+            per_source[source]["note_state"] = 1; # some
+        else:
+            per_source[source]["note_state"] = 2; # all
+        per_source[source]["list"].sort(indiv_sg_compare)
+    per_source_items = per_source.items()
+    per_source_items.sort(sg_compare)
+    for i in per_source_items:
+        for j in i[1]["list"]:
+            sorted_list.append(j["filename"])
+    return sorted_list
+
+################################################################################
+
+class Section_Completer:
+    def __init__ (self):
+        self.sections = []
+        q = projectB.query("SELECT section FROM section")
+        for i in q.getresult():
+            self.sections.append(i[0])
+
+    def complete(self, text, state):
+        if state == 0:
+            self.matches = []
+            n = len(text)
+            for word in self.sections:
+                if word[:n] == text:
+                    self.matches.append(word)
+        try:
+            return self.matches[state]
+        except IndexError:
+            return None
+
+############################################################
+
+class Priority_Completer:
+    def __init__ (self):
+        self.priorities = []
+        q = projectB.query("SELECT priority FROM priority")
+        for i in q.getresult():
+            self.priorities.append(i[0])
+
+    def complete(self, text, state):
+        if state == 0:
+            self.matches = []
+            n = len(text)
+            for word in self.priorities:
+                if word[:n] == text:
+                    self.matches.append(word)
+        try:
+            return self.matches[state]
+        except IndexError:
+            return None
+
+################################################################################
+
+def print_new (new, indexed, file=sys.stdout):
+    queue.check_valid(new)
+    broken = 0
+    index = 0
+    for pkg in new.keys():
+        index += 1
+        section = new[pkg]["section"]
+        priority = new[pkg]["priority"]
+        if new[pkg]["section id"] == -1:
+            section += "[!]"
+            broken = 1
+        if new[pkg]["priority id"] == -1:
+            priority += "[!]"
+            broken = 1
+        if indexed:
+            line = "(%s): %-20s %-20s %-20s" % (index, pkg, priority, section)
+        else:
+            line = "%-20s %-20s %-20s" % (pkg, priority, section)
+        line = line.strip()+'\n'
+        file.write(line)
+    note = Upload.pkg.changes.get("process-new note")
+    if note:
+        print "*"*75
+        print note
+        print "*"*75
+    return broken, note
+
+################################################################################
+
+def index_range (index):
+    if index == 1:
+        return "1"
+    else:
+        return "1-%s" % (index)
+
+################################################################################
+################################################################################
+
+def edit_new (new):
+    # Write the current data to a temporary file
+    temp_filename = utils.temp_filename()
+    temp_file = utils.open_file(temp_filename, 'w')
+    print_new (new, 0, temp_file)
+    temp_file.close()
+    # Spawn an editor on that file
+    editor = os.environ.get("EDITOR","vi")
+    result = os.system("%s %s" % (editor, temp_filename))
+    if result != 0:
+        utils.fubar ("%s invocation failed for %s." % (editor, temp_filename), result)
+    # Read the edited data back in
+    temp_file = utils.open_file(temp_filename)
+    lines = temp_file.readlines()
+    temp_file.close()
+    os.unlink(temp_filename)
+    # Parse the new data
+    for line in lines:
+        line = line.strip()
+        if line == "":
+            continue
+        s = line.split()
+        # Pad the list if necessary
+        s[len(s):3] = [None] * (3-len(s))
+        (pkg, priority, section) = s[:3]
+        if not new.has_key(pkg):
+            utils.warn("Ignoring unknown package '%s'" % (pkg))
+        else:
+            # Strip off any invalid markers, print_new will readd them.
+            if section.endswith("[!]"):
+                section = section[:-3]
+            if priority.endswith("[!]"):
+                priority = priority[:-3]
+            for f in new[pkg]["files"]:
+                Upload.pkg.files[f]["section"] = section
+                Upload.pkg.files[f]["priority"] = priority
+            new[pkg]["section"] = section
+            new[pkg]["priority"] = priority
+
+################################################################################
+
+def edit_index (new, index):
+    priority = new[index]["priority"]
+    section = new[index]["section"]
+    ftype = new[index]["type"]
+    done = 0
+    while not done:
+        print "\t".join([index, priority, section])
+
+        answer = "XXX"
+        if ftype != "dsc":
+            prompt = "[B]oth, Priority, Section, Done ? "
+        else:
+            prompt = "[S]ection, Done ? "
+        edit_priority = edit_section = 0
+
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = queue.re_default_answer.match(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+
+        if answer == 'P':
+            edit_priority = 1
+        elif answer == 'S':
+            edit_section = 1
+        elif answer == 'B':
+            edit_priority = edit_section = 1
+        elif answer == 'D':
+            done = 1
+
+        # Edit the priority
+        if edit_priority:
+            readline.set_completer(Priorities.complete)
+            got_priority = 0
+            while not got_priority:
+                new_priority = utils.our_raw_input("New priority: ").strip()
+                if new_priority not in Priorities.priorities:
+                    print "E: '%s' is not a valid priority, try again." % (new_priority)
+                else:
+                    got_priority = 1
+                    priority = new_priority
+
+        # Edit the section
+        if edit_section:
+            readline.set_completer(Sections.complete)
+            got_section = 0
+            while not got_section:
+                new_section = utils.our_raw_input("New section: ").strip()
+                if new_section not in Sections.sections:
+                    print "E: '%s' is not a valid section, try again." % (new_section)
+                else:
+                    got_section = 1
+                    section = new_section
+
+        # Reset the readline completer
+        readline.set_completer(None)
+
+    for f in new[index]["files"]:
+        Upload.pkg.files[f]["section"] = section
+        Upload.pkg.files[f]["priority"] = priority
+    new[index]["priority"] = priority
+    new[index]["section"] = section
+    return new
+
+################################################################################
+
+def edit_overrides (new):
+    print
+    done = 0
+    while not done:
+        print_new (new, 1)
+        new_index = {}
+        index = 0
+        for i in new.keys():
+            index += 1
+            new_index[index] = i
+
+        prompt = "(%s) edit override <n>, Editor, Done ? " % (index_range(index))
+
+        got_answer = 0
+        while not got_answer:
+            answer = utils.our_raw_input(prompt)
+            if not answer.isdigit():
+                answer = answer[:1].upper()
+            if answer == "E" or answer == "D":
+                got_answer = 1
+            elif queue.re_isanum.match (answer):
+                answer = int(answer)
+                if (answer < 1) or (answer > index):
+                    print "%s is not a valid index (%s).  Please retry." % (answer, index_range(index))
+                else:
+                    got_answer = 1
+
+        if answer == 'E':
+            edit_new(new)
+        elif answer == 'D':
+            done = 1
+        else:
+            edit_index (new, new_index[answer])
+
+    return new
+
+################################################################################
+
+def edit_note(note):
+    # Write the current data to a temporary file
+    temp_filename = utils.temp_filename()
+    temp_file = utils.open_file(temp_filename, 'w')
+    temp_file.write(note)
+    temp_file.close()
+    editor = os.environ.get("EDITOR","vi")
+    answer = 'E'
+    while answer == 'E':
+        os.system("%s %s" % (editor, temp_filename))
+        temp_file = utils.open_file(temp_filename)
+        note = temp_file.read().rstrip()
+        temp_file.close()
+        print "Note:"
+        print utils.prefix_multi_line_string(note,"  ")
+        prompt = "[D]one, Edit, Abandon, Quit ?"
+        answer = "XXX"
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = queue.re_default_answer.search(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+    os.unlink(temp_filename)
+    if answer == 'A':
+        return
+    elif answer == 'Q':
+        end()
+        sys.exit(0)
+    Upload.pkg.changes["process-new note"] = note
+    Upload.dump_vars(Cnf["Dir::Queue::New"])
+
+################################################################################
+
+def check_pkg ():
+    try:
+        less_fd = os.popen("less -R -", 'w', 0)
+        stdout_fd = sys.stdout
+        try:
+            sys.stdout = less_fd
+            examine_package.display_changes(Upload.pkg.changes_file)
+            files = Upload.pkg.files
+            for f in files.keys():
+                if files[f].has_key("new"):
+                    ftype = files[f]["type"]
+                    if ftype == "deb":
+                        examine_package.check_deb(f)
+                    elif ftype == "dsc":
+                        examine_package.check_dsc(f)
+        finally:
+            sys.stdout = stdout_fd
+    except IOError, e:
+        if e.errno == errno.EPIPE:
+            utils.warn("[examine_package] Caught EPIPE; skipping.")
+            pass
+        else:
+            raise
+    except KeyboardInterrupt:
+        utils.warn("[examine_package] Caught C-c; skipping.")
+        pass
+
+################################################################################
+
+## FIXME: horribly Debian specific
+
+def do_bxa_notification():
+    files = Upload.pkg.files
+    summary = ""
+    for f in files.keys():
+        if files[f]["type"] == "deb":
+            control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(f)))
+            summary += "\n"
+            summary += "Package: %s\n" % (control.Find("Package"))
+            summary += "Description: %s\n" % (control.Find("Description"))
+    Upload.Subst["__BINARY_DESCRIPTIONS__"] = summary
+    bxa_mail = utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/process-new.bxa_notification")
+    utils.send_mail(bxa_mail)
+
+################################################################################
+
+def add_overrides (new):
+    changes = Upload.pkg.changes
+    files = Upload.pkg.files
+
+    projectB.query("BEGIN WORK")
+    for suite in changes["suite"].keys():
+        suite_id = database.get_suite_id(suite)
+        for pkg in new.keys():
+            component_id = database.get_component_id(new[pkg]["component"])
+            type_id = database.get_override_type_id(new[pkg]["type"])
+            priority_id = new[pkg]["priority id"]
+            section_id = new[pkg]["section id"]
+            projectB.query("INSERT INTO override (suite, component, type, package, priority, section, maintainer) VALUES (%s, %s, %s, '%s', %s, %s, '')" % (suite_id, component_id, type_id, pkg, priority_id, section_id))
+            for f in new[pkg]["files"]:
+                if files[f].has_key("new"):
+                    del files[f]["new"]
+            del new[pkg]
+
+    projectB.query("COMMIT WORK")
+
+    if Cnf.FindB("Dinstall::BXANotify"):
+        do_bxa_notification()
+
+################################################################################
+
+def prod_maintainer ():
+    # Here we prepare an editor and get them ready to prod...
+    temp_filename = utils.temp_filename()
+    editor = os.environ.get("EDITOR","vi")
+    answer = 'E'
+    while answer == 'E':
+        os.system("%s %s" % (editor, temp_filename))
+        f = utils.open_file(temp_filename)
+        prod_message = "".join(f.readlines())
+        f.close()
+        print "Prod message:"
+        print utils.prefix_multi_line_string(prod_message,"  ",include_blank_lines=1)
+        prompt = "[P]rod, Edit, Abandon, Quit ?"
+        answer = "XXX"
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = queue.re_default_answer.search(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+        os.unlink(temp_filename)
+        if answer == 'A':
+            return
+        elif answer == 'Q':
+            end()
+            sys.exit(0)
+    # Otherwise, do the proding...
+    user_email_address = utils.whoami() + " <%s>" % (
+        Cnf["Dinstall::MyAdminAddress"])
+
+    Subst = Upload.Subst
+
+    Subst["__FROM_ADDRESS__"] = user_email_address
+    Subst["__PROD_MESSAGE__"] = prod_message
+    Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
+
+    prod_mail_message = utils.TemplateSubst(
+        Subst,Cnf["Dir::Templates"]+"/process-new.prod")
+
+    # Send the prod mail if appropriate
+    if not Cnf["Dinstall::Options::No-Mail"]:
+        utils.send_mail(prod_mail_message)
+
+    print "Sent proding message"
+
+################################################################################
+
+def do_new():
+    print "NEW\n"
+    files = Upload.pkg.files
+    changes = Upload.pkg.changes
+
+    # Make a copy of distribution we can happily trample on
+    changes["suite"] = copy.copy(changes["distribution"])
+
+    # Fix up the list of target suites
+    for suite in changes["suite"].keys():
+        override = Cnf.Find("Suite::%s::OverrideSuite" % (suite))
+        if override:
+            (olderr, newerr) = (database.get_suite_id(suite) == -1,
+              database.get_suite_id(override) == -1)
+            if olderr or newerr:
+                (oinv, newinv) = ("", "")
+                if olderr: oinv = "invalid "
+                if newerr: ninv = "invalid "
+                print "warning: overriding %ssuite %s to %ssuite %s" % (
+                        oinv, suite, ninv, override)
+            del changes["suite"][suite]
+            changes["suite"][override] = 1
+    # Validate suites
+    for suite in changes["suite"].keys():
+        suite_id = database.get_suite_id(suite)
+        if suite_id == -1:
+            utils.fubar("%s has invalid suite '%s' (possibly overriden).  say wha?" % (changes, suite))
+
+    # The main NEW processing loop
+    done = 0
+    while not done:
+        # Find out what's new
+        new = queue.determine_new(changes, files, projectB)
+
+        if not new:
+            break
+
+        answer = "XXX"
+        if Options["No-Action"] or Options["Automatic"]:
+            answer = 'S'
+
+        (broken, note) = print_new(new, 0)
+        prompt = ""
+
+        if not broken and not note:
+            prompt = "Add overrides, "
+        if broken:
+            print "W: [!] marked entries must be fixed before package can be processed."
+        if note:
+            print "W: note must be removed before package can be processed."
+            prompt += "Remove note, "
+
+        prompt += "Edit overrides, Check, Manual reject, Note edit, Prod, [S]kip, Quit ?"
+
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = queue.re_default_answer.search(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+
+        if answer == 'A':
+            done = add_overrides (new)
+        elif answer == 'C':
+            check_pkg()
+        elif answer == 'E':
+            new = edit_overrides (new)
+        elif answer == 'M':
+            aborted = Upload.do_reject(1, Options["Manual-Reject"])
+            if not aborted:
+                os.unlink(Upload.pkg.changes_file[:-8]+".dak")
+                done = 1
+        elif answer == 'N':
+            edit_note(changes.get("process-new note", ""))
+        elif answer == 'P':
+            prod_maintainer()
+        elif answer == 'R':
+            confirm = utils.our_raw_input("Really clear note (y/N)? ").lower()
+            if confirm == "y":
+                del changes["process-new note"]
+        elif answer == 'S':
+            done = 1
+        elif answer == 'Q':
+            end()
+            sys.exit(0)
+
+################################################################################
+################################################################################
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak process-new [OPTION]... [CHANGES]...
+  -a, --automatic           automatic run
+  -h, --help                show this help and exit.
+  -C, --comments-dir=DIR    use DIR as comments-dir, for [o-]p-u-new
+  -m, --manual-reject=MSG   manual reject with `msg'
+  -n, --no-action           don't do anything
+  -V, --version             display the version number and exit"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def init():
+    global Cnf, Options, Logger, Upload, projectB, Sections, Priorities
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('a',"automatic","Process-New::Options::Automatic"),
+                 ('h',"help","Process-New::Options::Help"),
+                 ('C',"comments-dir","Process-New::Options::Comments-Dir", "HasArg"),
+                 ('m',"manual-reject","Process-New::Options::Manual-Reject", "HasArg"),
+                 ('n',"no-action","Process-New::Options::No-Action")]
+
+    for i in ["automatic", "help", "manual-reject", "no-action", "version", "comments-dir"]:
+        if not Cnf.has_key("Process-New::Options::%s" % (i)):
+            Cnf["Process-New::Options::%s" % (i)] = ""
+
+    changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Process-New::Options")
+
+    if Options["Help"]:
+        usage()
+
+    Upload = queue.Upload(Cnf)
+
+    if not Options["No-Action"]:
+        Logger = Upload.Logger = logging.Logger(Cnf, "process-new")
+
+    projectB = Upload.projectB
+
+    Sections = Section_Completer()
+    Priorities = Priority_Completer()
+    readline.parse_and_bind("tab: complete")
+
+    return changes_files
+
+################################################################################
+
+def do_byhand():
+    done = 0
+    while not done:
+        files = Upload.pkg.files
+        will_install = 1
+        byhand = []
+
+        for f in files.keys():
+            if files[f]["type"] == "byhand":
+                if os.path.exists(f):
+                    print "W: %s still present; please process byhand components and try again." % (f)
+                    will_install = 0
+                else:
+                    byhand.append(f)
+
+        answer = "XXXX"
+        if Options["No-Action"]:
+            answer = "S"
+        if will_install:
+            if Options["Automatic"] and not Options["No-Action"]:
+                answer = 'A'
+            prompt = "[A]ccept, Manual reject, Skip, Quit ?"
+        else:
+            prompt = "Manual reject, [S]kip, Quit ?"
+
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = queue.re_default_answer.search(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+
+        if answer == 'A':
+            done = 1
+            for f in byhand:
+                del files[f]
+        elif answer == 'M':
+            Upload.do_reject(1, Options["Manual-Reject"])
+            os.unlink(Upload.pkg.changes_file[:-8]+".dak")
+            done = 1
+        elif answer == 'S':
+            done = 1
+        elif answer == 'Q':
+            end()
+            sys.exit(0)
+
+################################################################################
+
+def get_accept_lock():
+    retry = 0
+    while retry < 10:
+        try:
+            os.open(Cnf["Process-New::AcceptedLockFile"], os.O_RDONLY | os.O_CREAT | os.O_EXCL)
+            retry = 10
+        except OSError, e:
+            if e.errno == errno.EACCES or e.errno == errno.EEXIST:
+                retry += 1
+                if (retry >= 10):
+                    utils.fubar("Couldn't obtain lock; assuming 'dak process-unchecked' is already running.")
+                else:
+                    print("Unable to get accepted lock (try %d of 10)" % retry)
+                time.sleep(60)
+            else:
+                raise
+
+def move_to_dir (dest, perms=0660, changesperms=0664):
+    utils.move (Upload.pkg.changes_file, dest, perms=changesperms)
+    file_keys = Upload.pkg.files.keys()
+    for f in file_keys:
+        utils.move (f, dest, perms=perms)
+
+def is_source_in_queue_dir(qdir):
+    entries = [ x for x in os.listdir(qdir) if x.startswith(Upload.pkg.changes["source"])
+                and x.endswith(".changes") ]
+    for entry in entries:
+        # read the .dak
+        u = queue.Upload(Cnf)
+        u.pkg.changes_file = os.path.join(qdir, entry)
+        u.update_vars()
+        if not u.pkg.changes["architecture"].has_key("source"):
+            # another binary upload, ignore
+            continue
+        if Upload.pkg.changes["version"] != u.pkg.changes["version"]:
+            # another version, ignore
+            continue
+        # found it!
+        return True
+    return False
+
+def move_to_holding(suite, queue_dir):
+    print "Moving to %s holding area." % (suite.upper(),)
+    if Options["No-Action"]:
+       return
+    Logger.log(["Moving to %s" % (suite,), Upload.pkg.changes_file])
+    Upload.dump_vars(queue_dir)
+    move_to_dir(queue_dir)
+    os.unlink(Upload.pkg.changes_file[:-8]+".dak")
+
+def _accept():
+    if Options["No-Action"]:
+        return
+    (summary, short_summary) = Upload.build_summaries()
+    Upload.accept(summary, short_summary)
+    os.unlink(Upload.pkg.changes_file[:-8]+".dak")
+
+def do_accept_stableupdate(suite, q):
+    queue_dir = Cnf["Dir::Queue::%s" % (q,)]
+    if not Upload.pkg.changes["architecture"].has_key("source"):
+        # It is not a sourceful upload.  So its source may be either in p-u
+        # holding, in new, in accepted or already installed.
+        if is_source_in_queue_dir(queue_dir):
+            # It's in p-u holding, so move it there.
+            print "Binary-only upload, source in %s." % (q,)
+            move_to_holding(suite, queue_dir)
+        elif Upload.source_exists(Upload.pkg.changes["source"],
+                Upload.pkg.changes["version"]):
+            # dak tells us that there is source available.  At time of
+            # writing this means that it is installed, so put it into
+            # accepted.
+            print "Binary-only upload, source installed."
+            _accept()
+        elif is_source_in_queue_dir(Cnf["Dir::Queue::Accepted"]):
+            # The source is in accepted, the binary cleared NEW: accept it.
+            print "Binary-only upload, source in accepted."
+            _accept()
+        elif is_source_in_queue_dir(Cnf["Dir::Queue::New"]):
+            # It's in NEW.  We expect the source to land in p-u holding
+            # pretty soon.
+            print "Binary-only upload, source in new."
+            move_to_holding(suite, queue_dir)
+        else:
+            # No case applicable.  Bail out.  Return will cause the upload
+            # to be skipped.
+            print "ERROR"
+            print "Stable update failed.  Source not found."
+            return
+    else:
+        # We are handling a sourceful upload.  Move to accepted if currently
+        # in p-u holding and to p-u holding otherwise.
+        if is_source_in_queue_dir(queue_dir):
+            print "Sourceful upload in %s, accepting." % (q,)
+            _accept()
+        else:
+            move_to_holding(suite, queue_dir)
+
+def do_accept():
+    print "ACCEPT"
+    if not Options["No-Action"]:
+        get_accept_lock()
+        (summary, short_summary) = Upload.build_summaries()
+    try:
+        if Cnf.FindB("Dinstall::SecurityQueueHandling"):
+            Upload.dump_vars(Cnf["Dir::Queue::Embargoed"])
+            move_to_dir(Cnf["Dir::Queue::Embargoed"])
+            Upload.queue_build("embargoed", Cnf["Dir::Queue::Embargoed"])
+            # Check for override disparities
+            Upload.Subst["__SUMMARY__"] = summary
+        else:
+            # Stable updates need to be copied to proposed-updates holding
+            # area instead of accepted.  Sourceful uploads need to go
+            # to it directly, binaries only if the source has not yet been
+            # accepted into p-u.
+            for suite, q in [("proposed-updates", "ProposedUpdates"),
+                    ("oldstable-proposed-updates", "OldProposedUpdates")]:
+                if not Upload.pkg.changes["distribution"].has_key(suite):
+                    continue
+                return do_accept_stableupdate(suite, q)
+            # Just a normal upload, accept it...
+            _accept()
+    finally:
+        if not Options["No-Action"]:
+            os.unlink(Cnf["Process-New::AcceptedLockFile"])
+
+def check_status(files):
+    new = byhand = 0
+    for f in files.keys():
+        if files[f]["type"] == "byhand":
+            byhand = 1
+        elif files[f].has_key("new"):
+            new = 1
+    return (new, byhand)
+
+def do_pkg(changes_file):
+    Upload.pkg.changes_file = changes_file
+    Upload.init_vars()
+    Upload.update_vars()
+    Upload.update_subst()
+    files = Upload.pkg.files
+
+    if not recheck():
+        return
+
+    (new, byhand) = check_status(files)
+    if new or byhand:
+        if new:
+            do_new()
+        if byhand:
+            do_byhand()
+        (new, byhand) = check_status(files)
+
+    if not new and not byhand:
+        do_accept()
+
+################################################################################
+
+def end():
+    accept_count = Upload.accept_count
+    accept_bytes = Upload.accept_bytes
+
+    if accept_count:
+        sets = "set"
+        if accept_count > 1:
+            sets = "sets"
+        sys.stderr.write("Accepted %d package %s, %s.\n" % (accept_count, sets, utils.size_type(int(accept_bytes))))
+        Logger.log(["total",accept_count,accept_bytes])
+
+    if not Options["No-Action"]:
+        Logger.close()
+
+################################################################################
+
+def do_comments(dir, opref, npref, line, fn):
+    for comm in [ x for x in os.listdir(dir) if x.startswith(opref) ]:
+        lines = open("%s/%s" % (dir, comm)).readlines()
+        if len(lines) == 0 or lines[0] != line + "\n": continue
+        changes_files = [ x for x in os.listdir(".") if x.startswith(comm[7:]+"_")
+                                and x.endswith(".changes") ]
+        changes_files = sort_changes(changes_files)
+        for f in changes_files:
+            f = utils.validate_changes_file_arg(f, 0)
+            if not f: continue
+            print "\n" + f
+            fn(f, "".join(lines[1:]))
+
+        if opref != npref and not Options["No-Action"]:
+            newcomm = npref + comm[len(opref):]
+            os.rename("%s/%s" % (dir, comm), "%s/%s" % (dir, newcomm))
+
+################################################################################
+
+def comment_accept(changes_file, comments):
+    Upload.pkg.changes_file = changes_file
+    Upload.init_vars()
+    Upload.update_vars()
+    Upload.update_subst()
+    files = Upload.pkg.files
+
+    if not recheck():
+        return # dak wants to REJECT, crap
+
+    (new, byhand) = check_status(files)
+    if not new and not byhand:
+        do_accept()
+
+################################################################################
+
+def comment_reject(changes_file, comments):
+    Upload.pkg.changes_file = changes_file
+    Upload.init_vars()
+    Upload.update_vars()
+    Upload.update_subst()
+
+    if not recheck():
+        pass # dak has its own reasons to reject as well, which is fine
+
+    reject(comments)
+    print "REJECT\n" + reject_message,
+    if not Options["No-Action"]:
+        Upload.do_reject(0, reject_message)
+        os.unlink(Upload.pkg.changes_file[:-8]+".dak")
+
+################################################################################
+
+def main():
+    changes_files = init()
+    if len(changes_files) > 50:
+        sys.stderr.write("Sorting changes...\n")
+    changes_files = sort_changes(changes_files)
+
+    # Kill me now? **FIXME**
+    Cnf["Dinstall::Options::No-Mail"] = ""
+    bcc = "X-DAK: dak process-new\nX-Katie: lisa $Revision: 1.31 $"
+    if Cnf.has_key("Dinstall::Bcc"):
+        Upload.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
+    else:
+        Upload.Subst["__BCC__"] = bcc
+
+    commentsdir = Cnf.get("Process-New::Options::Comments-Dir","")
+    if commentsdir:
+        if changes_files != []:
+            sys.stderr.write("Can't specify any changes files if working with comments-dir")
+            sys.exit(1)
+        do_comments(commentsdir, "ACCEPT.", "ACCEPTED.", "OK", comment_accept)
+        do_comments(commentsdir, "REJECT.", "REJECTED.", "NOTOK", comment_reject)
+    else:
+        for changes_file in changes_files:
+            changes_file = utils.validate_changes_file_arg(changes_file, 0)
+            if not changes_file:
+                continue
+            print "\n" + changes_file
+            do_pkg (changes_file)
+
+    end()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/process_unchecked.py b/dak/process_unchecked.py
new file mode 100755 (executable)
index 0000000..690c1c1
--- /dev/null
@@ -0,0 +1,1590 @@
+#!/usr/bin/env python
+
+# Checks Debian packages from Incoming
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+# Originally based on dinstall by Guy Maor <maor@debian.org>
+
+################################################################################
+
+# Computer games don't affect kids. I mean if Pacman affected our generation as
+# kids, we'd all run around in a darkened room munching pills and listening to
+# repetitive music.
+#         -- Unknown
+
+################################################################################
+
+import commands, errno, fcntl, os, re, shutil, stat, sys, time, tempfile, traceback
+import apt_inst, apt_pkg
+from daklib import database
+from daklib import logging
+from daklib import queue
+from daklib import utils
+from daklib.dak_exceptions import *
+
+from types import *
+
+################################################################################
+
+re_valid_version = re.compile(r"^([0-9]+:)?[0-9A-Za-z\.\-\+:~]+$")
+re_valid_pkg_name = re.compile(r"^[\dA-Za-z][\dA-Za-z\+\-\.]+$")
+re_changelog_versions = re.compile(r"^\w[-+0-9a-z.]+ \([^\(\) \t]+\)")
+re_strip_revision = re.compile(r"-([^-]+)$")
+re_strip_srcver = re.compile(r"\s+\(\S+\)$")
+re_spacestrip = re.compile('(\s)')
+
+################################################################################
+
+# Globals
+Cnf = None
+Options = None
+Logger = None
+Upload = None
+
+reprocess = 0
+in_holding = {}
+
+# Aliases to the real vars in the Upload class; hysterical raisins.
+reject_message = ""
+changes = {}
+dsc = {}
+dsc_files = {}
+files = {}
+pkg = {}
+
+###############################################################################
+
+def init():
+    global Cnf, Options, Upload, changes, dsc, dsc_files, files, pkg
+
+    apt_pkg.init()
+
+    Cnf = apt_pkg.newConfiguration()
+    apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file())
+
+    Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
+                 ('h',"help","Dinstall::Options::Help"),
+                 ('n',"no-action","Dinstall::Options::No-Action"),
+                 ('p',"no-lock", "Dinstall::Options::No-Lock"),
+                 ('s',"no-mail", "Dinstall::Options::No-Mail")]
+
+    for i in ["automatic", "help", "no-action", "no-lock", "no-mail",
+              "override-distribution", "version"]:
+        Cnf["Dinstall::Options::%s" % (i)] = ""
+
+    changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Dinstall::Options")
+
+    if Options["Help"]:
+        usage()
+
+    Upload = queue.Upload(Cnf)
+
+    changes = Upload.pkg.changes
+    dsc = Upload.pkg.dsc
+    dsc_files = Upload.pkg.dsc_files
+    files = Upload.pkg.files
+    pkg = Upload.pkg
+
+    return changes_files
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dinstall [OPTION]... [CHANGES]...
+  -a, --automatic           automatic run
+  -h, --help                show this help and exit.
+  -n, --no-action           don't do anything
+  -p, --no-lock             don't check lockfile !! for cron.daily only !!
+  -s, --no-mail             don't send any mail
+  -V, --version             display the version number and exit"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def reject (str, prefix="Rejected: "):
+    global reject_message
+    if str:
+        reject_message += prefix + str + "\n"
+
+################################################################################
+
+def copy_to_holding(filename):
+    global in_holding
+
+    base_filename = os.path.basename(filename)
+
+    dest = Cnf["Dir::Queue::Holding"] + '/' + base_filename
+    try:
+        fd = os.open(dest, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0640)
+        os.close(fd)
+    except OSError, e:
+        # Shouldn't happen, but will if, for example, someone lists a
+        # file twice in the .changes.
+        if errno.errorcode[e.errno] == 'EEXIST':
+            reject("%s: already exists in holding area; can not overwrite." % (base_filename))
+            return
+        raise
+
+    try:
+        shutil.copy(filename, dest)
+    except IOError, e:
+        # In either case (ENOENT or EACCES) we want to remove the
+        # O_CREAT | O_EXCLed ghost file, so add the file to the list
+        # of 'in holding' even if it's not the real file.
+        if errno.errorcode[e.errno] == 'ENOENT':
+            reject("%s: can not copy to holding area: file not found." % (base_filename))
+            os.unlink(dest)
+            return
+        elif errno.errorcode[e.errno] == 'EACCES':
+            reject("%s: can not copy to holding area: read permission denied." % (base_filename))
+            os.unlink(dest)
+            return
+        raise
+
+    in_holding[base_filename] = ""
+
+################################################################################
+
+def clean_holding():
+    global in_holding
+
+    cwd = os.getcwd()
+    os.chdir(Cnf["Dir::Queue::Holding"])
+    for f in in_holding.keys():
+        if os.path.exists(f):
+            if f.find('/') != -1:
+                utils.fubar("WTF? clean_holding() got a file ('%s') with / in it!" % (f))
+            else:
+                os.unlink(f)
+    in_holding = {}
+    os.chdir(cwd)
+
+################################################################################
+
+def check_changes():
+    filename = pkg.changes_file
+
+    # Parse the .changes field into a dictionary
+    try:
+        changes.update(utils.parse_changes(filename))
+    except CantOpenError:
+        reject("%s: can't read file." % (filename))
+        return 0
+    except ParseChangesError, line:
+        reject("%s: parse error, can't grok: %s." % (filename, line))
+        return 0
+
+    # Parse the Files field from the .changes into another dictionary
+    try:
+        files.update(utils.build_file_list(changes))
+    except ParseChangesError, line:
+        reject("%s: parse error, can't grok: %s." % (filename, line))
+    except UnknownFormatError, format:
+        reject("%s: unknown format '%s'." % (filename, format))
+        return 0
+
+    # Check for mandatory fields
+    for i in ("source", "binary", "architecture", "version", "distribution",
+              "maintainer", "files", "changes", "description"):
+        if not changes.has_key(i):
+            reject("%s: Missing mandatory field `%s'." % (filename, i))
+            return 0    # Avoid <undef> errors during later tests
+
+    # Strip a source version in brackets from the source field
+    if re_strip_srcver.search(changes["source"]):
+        changes["source"] = re_strip_srcver.sub('', changes["source"])
+
+    # Ensure the source field is a valid package name.
+    if not re_valid_pkg_name.match(changes["source"]):
+        reject("%s: invalid source name '%s'." % (filename, changes["source"]))
+
+    # Split multi-value fields into a lower-level dictionary
+    for i in ("architecture", "distribution", "binary", "closes"):
+        o = changes.get(i, "")
+        if o != "":
+            del changes[i]
+        changes[i] = {}
+        for j in o.split():
+            changes[i][j] = 1
+
+    # Fix the Maintainer: field to be RFC822/2047 compatible
+    try:
+        (changes["maintainer822"], changes["maintainer2047"],
+         changes["maintainername"], changes["maintaineremail"]) = \
+         utils.fix_maintainer (changes["maintainer"])
+    except ParseMaintError, msg:
+        reject("%s: Maintainer field ('%s') failed to parse: %s" \
+               % (filename, changes["maintainer"], msg))
+
+    # ...likewise for the Changed-By: field if it exists.
+    try:
+        (changes["changedby822"], changes["changedby2047"],
+         changes["changedbyname"], changes["changedbyemail"]) = \
+         utils.fix_maintainer (changes.get("changed-by", ""))
+    except ParseMaintError, msg:
+        (changes["changedby822"], changes["changedby2047"],
+         changes["changedbyname"], changes["changedbyemail"]) = \
+         ("", "", "", "")
+        reject("%s: Changed-By field ('%s') failed to parse: %s" \
+               % (filename, changes["changed-by"], msg))
+
+    # Ensure all the values in Closes: are numbers
+    if changes.has_key("closes"):
+        for i in changes["closes"].keys():
+            if queue.re_isanum.match (i) == None:
+                reject("%s: `%s' from Closes field isn't a number." % (filename, i))
+
+
+    # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
+    changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
+    changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
+
+    # Check there isn't already a changes file of the same name in one
+    # of the queue directories.
+    base_filename = os.path.basename(filename)
+    for d in [ "Accepted", "Byhand", "Done", "New", "ProposedUpdates", "OldProposedUpdates" ]:
+        if os.path.exists(Cnf["Dir::Queue::%s" % (d) ]+'/'+base_filename):
+            reject("%s: a file with this name already exists in the %s directory." % (base_filename, d))
+
+    # Check the .changes is non-empty
+    if not files:
+        reject("%s: nothing to do (Files field is empty)." % (base_filename))
+        return 0
+
+    return 1
+
+################################################################################
+
+def check_distributions():
+    "Check and map the Distribution field of a .changes file."
+
+    # Handle suite mappings
+    for m in Cnf.ValueList("SuiteMappings"):
+        args = m.split()
+        mtype = args[0]
+        if mtype == "map" or mtype == "silent-map":
+            (source, dest) = args[1:3]
+            if changes["distribution"].has_key(source):
+                del changes["distribution"][source]
+                changes["distribution"][dest] = 1
+                if mtype != "silent-map":
+                    reject("Mapping %s to %s." % (source, dest),"")
+            if changes.has_key("distribution-version"):
+                if changes["distribution-version"].has_key(source):
+                    changes["distribution-version"][source]=dest
+        elif mtype == "map-unreleased":
+            (source, dest) = args[1:3]
+            if changes["distribution"].has_key(source):
+                for arch in changes["architecture"].keys():
+                    if arch not in Cnf.ValueList("Suite::%s::Architectures" % (source)):
+                        reject("Mapping %s to %s for unreleased architecture %s." % (source, dest, arch),"")
+                        del changes["distribution"][source]
+                        changes["distribution"][dest] = 1
+                        break
+        elif mtype == "ignore":
+            suite = args[1]
+            if changes["distribution"].has_key(suite):
+                del changes["distribution"][suite]
+                reject("Ignoring %s as a target suite." % (suite), "Warning: ")
+        elif mtype == "reject":
+            suite = args[1]
+            if changes["distribution"].has_key(suite):
+                reject("Uploads to %s are not accepted." % (suite))
+        elif mtype == "propup-version":
+            # give these as "uploaded-to(non-mapped) suites-to-add-when-upload-obsoletes"
+            #
+            # changes["distribution-version"] looks like: {'testing': 'testing-proposed-updates'}
+            if changes["distribution"].has_key(args[1]):
+                changes.setdefault("distribution-version", {})
+                for suite in args[2:]: changes["distribution-version"][suite]=suite
+
+    # Ensure there is (still) a target distribution
+    if changes["distribution"].keys() == []:
+        reject("no valid distribution.")
+
+    # Ensure target distributions exist
+    for suite in changes["distribution"].keys():
+        if not Cnf.has_key("Suite::%s" % (suite)):
+            reject("Unknown distribution `%s'." % (suite))
+
+################################################################################
+
+def check_deb_ar(filename):
+    """Sanity check the ar of a .deb, i.e. that there is:
+
+ o debian-binary
+ o control.tar.gz
+ o data.tar.gz or data.tar.bz2
+
+in that order, and nothing else."""
+    cmd = "ar t %s" % (filename)
+    (result, output) = commands.getstatusoutput(cmd)
+    if result != 0:
+        reject("%s: 'ar t' invocation failed." % (filename))
+        reject(utils.prefix_multi_line_string(output, " [ar output:] "), "")
+    chunks = output.split('\n')
+    if len(chunks) != 3:
+        reject("%s: found %d chunks, expected 3." % (filename, len(chunks)))
+    if chunks[0] != "debian-binary":
+        reject("%s: first chunk is '%s', expected 'debian-binary'." % (filename, chunks[0]))
+    if chunks[1] != "control.tar.gz":
+        reject("%s: second chunk is '%s', expected 'control.tar.gz'." % (filename, chunks[1]))
+    if chunks[2] not in [ "data.tar.bz2", "data.tar.gz" ]:
+        reject("%s: third chunk is '%s', expected 'data.tar.gz' or 'data.tar.bz2'." % (filename, chunks[2]))
+
+################################################################################
+
+def check_files():
+    global reprocess
+
+    archive = utils.where_am_i()
+    file_keys = files.keys()
+
+    # if reprocess is 2 we've already done this and we're checking
+    # things again for the new .orig.tar.gz.
+    # [Yes, I'm fully aware of how disgusting this is]
+    if not Options["No-Action"] and reprocess < 2:
+        cwd = os.getcwd()
+        os.chdir(pkg.directory)
+        for f in file_keys:
+            copy_to_holding(f)
+        os.chdir(cwd)
+
+    # Check there isn't already a .changes or .dak file of the same name in
+    # the proposed-updates "CopyChanges" or "CopyDotDak" storage directories.
+    # [NB: this check must be done post-suite mapping]
+    base_filename = os.path.basename(pkg.changes_file)
+    dot_dak_filename = base_filename[:-8]+".dak"
+    for suite in changes["distribution"].keys():
+        copychanges = "Suite::%s::CopyChanges" % (suite)
+        if Cnf.has_key(copychanges) and \
+               os.path.exists(Cnf[copychanges]+"/"+base_filename):
+            reject("%s: a file with this name already exists in %s" \
+                   % (base_filename, Cnf[copychanges]))
+
+        copy_dot_dak = "Suite::%s::CopyDotDak" % (suite)
+        if Cnf.has_key(copy_dot_dak) and \
+               os.path.exists(Cnf[copy_dot_dak]+"/"+dot_dak_filename):
+            reject("%s: a file with this name already exists in %s" \
+                   % (dot_dak_filename, Cnf[copy_dot_dak]))
+
+    reprocess = 0
+    has_binaries = 0
+    has_source = 0
+
+    for f in file_keys:
+        # Ensure the file does not already exist in one of the accepted directories
+        for d in [ "Accepted", "Byhand", "New", "ProposedUpdates", "OldProposedUpdates", "Embargoed", "Unembargoed" ]:
+            if not Cnf.has_key("Dir::Queue::%s" % (d)): continue
+            if os.path.exists(Cnf["Dir::Queue::%s" % (d) ] + '/' + f):
+                reject("%s file already exists in the %s directory." % (f, d))
+        if not utils.re_taint_free.match(f):
+            reject("!!WARNING!! tainted filename: '%s'." % (f))
+        # Check the file is readable
+        if os.access(f, os.R_OK) == 0:
+            # When running in -n, copy_to_holding() won't have
+            # generated the reject_message, so we need to.
+            if Options["No-Action"]:
+                if os.path.exists(f):
+                    reject("Can't read `%s'. [permission denied]" % (f))
+                else:
+                    reject("Can't read `%s'. [file not found]" % (f))
+            files[f]["type"] = "unreadable"
+            continue
+        # If it's byhand skip remaining checks
+        if files[f]["section"] == "byhand" or files[f]["section"][:4] == "raw-":
+            files[f]["byhand"] = 1
+            files[f]["type"] = "byhand"
+        # Checks for a binary package...
+        elif utils.re_isadeb.match(f):
+            has_binaries = 1
+            files[f]["type"] = "deb"
+
+            # Extract package control information
+            deb_file = utils.open_file(f)
+            try:
+                control = apt_pkg.ParseSection(apt_inst.debExtractControl(deb_file))
+            except:
+                reject("%s: debExtractControl() raised %s." % (f, sys.exc_type))
+                deb_file.close()
+                # Can't continue, none of the checks on control would work.
+                continue
+            deb_file.close()
+
+            # Check for mandatory fields
+            for field in [ "Package", "Architecture", "Version" ]:
+                if control.Find(field) == None:
+                    reject("%s: No %s field in control." % (f, field))
+                    # Can't continue
+                    continue
+
+            # Ensure the package name matches the one give in the .changes
+            if not changes["binary"].has_key(control.Find("Package", "")):
+                reject("%s: control file lists name as `%s', which isn't in changes file." % (f, control.Find("Package", "")))
+
+            # Validate the package field
+            package = control.Find("Package")
+            if not re_valid_pkg_name.match(package):
+                reject("%s: invalid package name '%s'." % (f, package))
+
+            # Validate the version field
+            version = control.Find("Version")
+            if not re_valid_version.match(version):
+                reject("%s: invalid version number '%s'." % (f, version))
+
+            # Ensure the architecture of the .deb is one we know about.
+            default_suite = Cnf.get("Dinstall::DefaultSuite", "Unstable")
+            architecture = control.Find("Architecture")
+            upload_suite = changes["distribution"].keys()[0]
+            if architecture not in Cnf.ValueList("Suite::%s::Architectures" % (default_suite)) and architecture not in Cnf.ValueList("Suite::%s::Architectures" % (upload_suite)):
+                reject("Unknown architecture '%s'." % (architecture))
+
+            # Ensure the architecture of the .deb is one of the ones
+            # listed in the .changes.
+            if not changes["architecture"].has_key(architecture):
+                reject("%s: control file lists arch as `%s', which isn't in changes file." % (f, architecture))
+
+            # Sanity-check the Depends field
+            depends = control.Find("Depends")
+            if depends == '':
+                reject("%s: Depends field is empty." % (f))
+
+            # Sanity-check the Provides field
+            provides = control.Find("Provides")
+            if provides:
+                provide = re_spacestrip.sub('', provides)
+                if provide == '':
+                    reject("%s: Provides field is empty." % (f))
+                prov_list = provide.split(",")
+                for prov in prov_list:
+                    if not re_valid_pkg_name.match(prov):
+                        reject("%s: Invalid Provides field content %s." % (f, prov))
+
+
+            # Check the section & priority match those given in the .changes (non-fatal)
+            if control.Find("Section") and files[f]["section"] != "" and files[f]["section"] != control.Find("Section"):
+                reject("%s control file lists section as `%s', but changes file has `%s'." % (f, control.Find("Section", ""), files[f]["section"]), "Warning: ")
+            if control.Find("Priority") and files[f]["priority"] != "" and files[f]["priority"] != control.Find("Priority"):
+                reject("%s control file lists priority as `%s', but changes file has `%s'." % (f, control.Find("Priority", ""), files[f]["priority"]),"Warning: ")
+
+            files[f]["package"] = package
+            files[f]["architecture"] = architecture
+            files[f]["version"] = version
+            files[f]["maintainer"] = control.Find("Maintainer", "")
+            if f.endswith(".udeb"):
+                files[f]["dbtype"] = "udeb"
+            elif f.endswith(".deb"):
+                files[f]["dbtype"] = "deb"
+            else:
+                reject("%s is neither a .deb or a .udeb." % (f))
+            files[f]["source"] = control.Find("Source", files[f]["package"])
+            # Get the source version
+            source = files[f]["source"]
+            source_version = ""
+            if source.find("(") != -1:
+                m = utils.re_extract_src_version.match(source)
+                source = m.group(1)
+                source_version = m.group(2)
+            if not source_version:
+                source_version = files[f]["version"]
+            files[f]["source package"] = source
+            files[f]["source version"] = source_version
+
+            # Ensure the filename matches the contents of the .deb
+            m = utils.re_isadeb.match(f)
+            #  package name
+            file_package = m.group(1)
+            if files[f]["package"] != file_package:
+                reject("%s: package part of filename (%s) does not match package name in the %s (%s)." % (f, file_package, files[f]["dbtype"], files[f]["package"]))
+            epochless_version = utils.re_no_epoch.sub('', control.Find("Version"))
+            #  version
+            file_version = m.group(2)
+            if epochless_version != file_version:
+                reject("%s: version part of filename (%s) does not match package version in the %s (%s)." % (f, file_version, files[f]["dbtype"], epochless_version))
+            #  architecture
+            file_architecture = m.group(3)
+            if files[f]["architecture"] != file_architecture:
+                reject("%s: architecture part of filename (%s) does not match package architecture in the %s (%s)." % (f, file_architecture, files[f]["dbtype"], files[f]["architecture"]))
+
+            # Check for existent source
+            source_version = files[f]["source version"]
+            source_package = files[f]["source package"]
+            if changes["architecture"].has_key("source"):
+                if source_version != changes["version"]:
+                    reject("source version (%s) for %s doesn't match changes version %s." % (source_version, f, changes["version"]))
+            else:
+                # Check in the SQL database
+                if not Upload.source_exists(source_package, source_version, changes["distribution"].keys()):
+                    # Check in one of the other directories
+                    source_epochless_version = utils.re_no_epoch.sub('', source_version)
+                    dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
+                    if os.path.exists(Cnf["Dir::Queue::Byhand"] + '/' + dsc_filename):
+                        files[f]["byhand"] = 1
+                    elif os.path.exists(Cnf["Dir::Queue::New"] + '/' + dsc_filename):
+                        files[f]["new"] = 1
+                    else:
+                        dsc_file_exists = 0
+                        for myq in ["Accepted", "Embargoed", "Unembargoed", "ProposedUpdates", "OldProposedUpdates"]:
+                            if Cnf.has_key("Dir::Queue::%s" % (myq)):
+                                if os.path.exists(Cnf["Dir::Queue::"+myq] + '/' + dsc_filename):
+                                    dsc_file_exists = 1
+                                    break
+                        if not dsc_file_exists:
+                            reject("no source found for %s %s (%s)." % (source_package, source_version, f))
+            # Check the version and for file overwrites
+            reject(Upload.check_binary_against_db(f),"")
+
+            check_deb_ar(f)
+
+        # Checks for a source package...
+        else:
+            m = utils.re_issource.match(f)
+            if m:
+                has_source = 1
+                files[f]["package"] = m.group(1)
+                files[f]["version"] = m.group(2)
+                files[f]["type"] = m.group(3)
+
+                # Ensure the source package name matches the Source filed in the .changes
+                if changes["source"] != files[f]["package"]:
+                    reject("%s: changes file doesn't say %s for Source" % (f, files[f]["package"]))
+
+                # Ensure the source version matches the version in the .changes file
+                if files[f]["type"] == "orig.tar.gz":
+                    changes_version = changes["chopversion2"]
+                else:
+                    changes_version = changes["chopversion"]
+                if changes_version != files[f]["version"]:
+                    reject("%s: should be %s according to changes file." % (f, changes_version))
+
+                # Ensure the .changes lists source in the Architecture field
+                if not changes["architecture"].has_key("source"):
+                    reject("%s: changes file doesn't list `source' in Architecture field." % (f))
+
+                # Check the signature of a .dsc file
+                if files[f]["type"] == "dsc":
+                    dsc["fingerprint"] = utils.check_signature(f, reject)
+
+                files[f]["architecture"] = "source"
+
+            # Not a binary or source package?  Assume byhand...
+            else:
+                files[f]["byhand"] = 1
+                files[f]["type"] = "byhand"
+
+        # Per-suite file checks
+        files[f]["oldfiles"] = {}
+        for suite in changes["distribution"].keys():
+            # Skip byhand
+            if files[f].has_key("byhand"):
+                continue
+
+            # Handle component mappings
+            for m in Cnf.ValueList("ComponentMappings"):
+                (source, dest) = m.split()
+                if files[f]["component"] == source:
+                    files[f]["original component"] = source
+                    files[f]["component"] = dest
+
+            # Ensure the component is valid for the target suite
+            if Cnf.has_key("Suite:%s::Components" % (suite)) and \
+               files[f]["component"] not in Cnf.ValueList("Suite::%s::Components" % (suite)):
+                reject("unknown component `%s' for suite `%s'." % (files[f]["component"], suite))
+                continue
+
+            # Validate the component
+            component = files[f]["component"]
+            component_id = database.get_component_id(component)
+            if component_id == -1:
+                reject("file '%s' has unknown component '%s'." % (f, component))
+                continue
+
+            # See if the package is NEW
+            if not Upload.in_override_p(files[f]["package"], files[f]["component"], suite, files[f].get("dbtype",""), f):
+                files[f]["new"] = 1
+
+            # Validate the priority
+            if files[f]["priority"].find('/') != -1:
+                reject("file '%s' has invalid priority '%s' [contains '/']." % (f, files[f]["priority"]))
+
+            # Determine the location
+            location = Cnf["Dir::Pool"]
+            location_id = database.get_location_id (location, component, archive)
+            if location_id == -1:
+                reject("[INTERNAL ERROR] couldn't determine location (Component: %s, Archive: %s)" % (component, archive))
+            files[f]["location id"] = location_id
+
+            # Check the md5sum & size against existing files (if any)
+            files[f]["pool name"] = utils.poolify (changes["source"], files[f]["component"])
+            files_id = database.get_files_id(files[f]["pool name"] + f, files[f]["size"], files[f]["md5sum"], files[f]["location id"])
+            if files_id == -1:
+                reject("INTERNAL ERROR, get_files_id() returned multiple matches for %s." % (f))
+            elif files_id == -2:
+                reject("md5sum and/or size mismatch on existing copy of %s." % (f))
+            files[f]["files id"] = files_id
+
+            # Check for packages that have moved from one component to another
+            q = Upload.projectB.query("""
+SELECT c.name FROM binaries b, bin_associations ba, suite s, location l,
+                   component c, architecture a, files f
+ WHERE b.package = '%s' AND s.suite_name = '%s'
+   AND (a.arch_string = '%s' OR a.arch_string = 'all')
+   AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
+   AND f.location = l.id AND l.component = c.id AND b.file = f.id"""
+                               % (files[f]["package"], suite,
+                                  files[f]["architecture"]))
+            ql = q.getresult()
+            if ql:
+                files[f]["othercomponents"] = ql[0][0]
+
+    # If the .changes file says it has source, it must have source.
+    if changes["architecture"].has_key("source"):
+        if not has_source:
+            reject("no source found and Architecture line in changes mention source.")
+
+        if not has_binaries and Cnf.FindB("Dinstall::Reject::NoSourceOnly"):
+            reject("source only uploads are not supported.")
+
+###############################################################################
+
+def check_dsc():
+    global reprocess
+
+    # Ensure there is source to check
+    if not changes["architecture"].has_key("source"):
+        return 1
+
+    # Find the .dsc
+    dsc_filename = None
+    for f in files.keys():
+        if files[f]["type"] == "dsc":
+            if dsc_filename:
+                reject("can not process a .changes file with multiple .dsc's.")
+                return 0
+            else:
+                dsc_filename = f
+
+    # If there isn't one, we have nothing to do. (We have reject()ed the upload already)
+    if not dsc_filename:
+        reject("source uploads must contain a dsc file")
+        return 0
+
+    # Parse the .dsc file
+    try:
+        dsc.update(utils.parse_changes(dsc_filename, signing_rules=1))
+    except CantOpenError:
+        # if not -n copy_to_holding() will have done this for us...
+        if Options["No-Action"]:
+            reject("%s: can't read file." % (dsc_filename))
+    except ParseChangesError, line:
+        reject("%s: parse error, can't grok: %s." % (dsc_filename, line))
+    except InvalidDscError, line:
+        reject("%s: syntax error on line %s." % (dsc_filename, line))
+    # Build up the file list of files mentioned by the .dsc
+    try:
+        dsc_files.update(utils.build_file_list(dsc, is_a_dsc=1))
+    except NoFilesFieldError:
+        reject("%s: no Files: field." % (dsc_filename))
+        return 0
+    except UnknownFormatError, format:
+        reject("%s: unknown format '%s'." % (dsc_filename, format))
+        return 0
+    except ParseChangesError, line:
+        reject("%s: parse error, can't grok: %s." % (dsc_filename, line))
+        return 0
+
+    # Enforce mandatory fields
+    for i in ("format", "source", "version", "binary", "maintainer", "architecture", "files"):
+        if not dsc.has_key(i):
+            reject("%s: missing mandatory field `%s'." % (dsc_filename, i))
+            return 0
+
+    # Validate the source and version fields
+    if not re_valid_pkg_name.match(dsc["source"]):
+        reject("%s: invalid source name '%s'." % (dsc_filename, dsc["source"]))
+    if not re_valid_version.match(dsc["version"]):
+        reject("%s: invalid version number '%s'." % (dsc_filename, dsc["version"]))
+
+    # Bumping the version number of the .dsc breaks extraction by stable's
+    # dpkg-source.  So let's not do that...
+    if dsc["format"] != "1.0":
+        reject("%s: incompatible 'Format' version produced by a broken version of dpkg-dev 1.9.1{3,4}." % (dsc_filename))
+
+    # Validate the Maintainer field
+    try:
+        utils.fix_maintainer (dsc["maintainer"])
+    except ParseMaintError, msg:
+        reject("%s: Maintainer field ('%s') failed to parse: %s" \
+               % (dsc_filename, dsc["maintainer"], msg))
+
+    # Validate the build-depends field(s)
+    for field_name in [ "build-depends", "build-depends-indep" ]:
+        field = dsc.get(field_name)
+        if field:
+            # Check for broken dpkg-dev lossage...
+            if field.startswith("ARRAY"):
+                reject("%s: invalid %s field produced by a broken version of dpkg-dev (1.10.11)" % (dsc_filename, field_name.title()))
+
+            # Have apt try to parse them...
+            try:
+                apt_pkg.ParseSrcDepends(field)
+            except:
+                reject("%s: invalid %s field (can not be parsed by apt)." % (dsc_filename, field_name.title()))
+                pass
+
+    # Ensure the version number in the .dsc matches the version number in the .changes
+    epochless_dsc_version = utils.re_no_epoch.sub('', dsc["version"])
+    changes_version = files[dsc_filename]["version"]
+    if epochless_dsc_version != files[dsc_filename]["version"]:
+        reject("version ('%s') in .dsc does not match version ('%s') in .changes." % (epochless_dsc_version, changes_version))
+
+    # Ensure there is a .tar.gz in the .dsc file
+    has_tar = 0
+    for f in dsc_files.keys():
+        m = utils.re_issource.match(f)
+        if not m:
+            reject("%s: %s in Files field not recognised as source." % (dsc_filename, f))
+            continue
+        ftype = m.group(3)
+        if ftype == "orig.tar.gz" or ftype == "tar.gz":
+            has_tar = 1
+    if not has_tar:
+        reject("%s: no .tar.gz or .orig.tar.gz in 'Files' field." % (dsc_filename))
+
+    # Ensure source is newer than existing source in target suites
+    reject(Upload.check_source_against_db(dsc_filename),"")
+
+    (reject_msg, is_in_incoming) = Upload.check_dsc_against_db(dsc_filename)
+    reject(reject_msg, "")
+    if is_in_incoming:
+        if not Options["No-Action"]:
+            copy_to_holding(is_in_incoming)
+        orig_tar_gz = os.path.basename(is_in_incoming)
+        files[orig_tar_gz] = {}
+        files[orig_tar_gz]["size"] = os.stat(orig_tar_gz)[stat.ST_SIZE]
+        files[orig_tar_gz]["md5sum"] = dsc_files[orig_tar_gz]["md5sum"]
+        files[orig_tar_gz]["sha1sum"] = dsc_files[orig_tar_gz]["sha1sum"]
+        files[orig_tar_gz]["sha256sum"] = dsc_files[orig_tar_gz]["sha256sum"]
+        files[orig_tar_gz]["section"] = files[dsc_filename]["section"]
+        files[orig_tar_gz]["priority"] = files[dsc_filename]["priority"]
+        files[orig_tar_gz]["component"] = files[dsc_filename]["component"]
+        files[orig_tar_gz]["type"] = "orig.tar.gz"
+        reprocess = 2
+
+    return 1
+
+################################################################################
+
+def get_changelog_versions(source_dir):
+    """Extracts a the source package and (optionally) grabs the
+    version history out of debian/changelog for the BTS."""
+
+    # Find the .dsc (again)
+    dsc_filename = None
+    for f in files.keys():
+        if files[f]["type"] == "dsc":
+            dsc_filename = f
+
+    # If there isn't one, we have nothing to do. (We have reject()ed the upload already)
+    if not dsc_filename:
+        return
+
+    # Create a symlink mirror of the source files in our temporary directory
+    for f in files.keys():
+        m = utils.re_issource.match(f)
+        if m:
+            src = os.path.join(source_dir, f)
+            # If a file is missing for whatever reason, give up.
+            if not os.path.exists(src):
+                return
+            ftype = m.group(3)
+            if ftype == "orig.tar.gz" and pkg.orig_tar_gz:
+                continue
+            dest = os.path.join(os.getcwd(), f)
+            os.symlink(src, dest)
+
+    # If the orig.tar.gz is not a part of the upload, create a symlink to the
+    # existing copy.
+    if pkg.orig_tar_gz:
+        dest = os.path.join(os.getcwd(), os.path.basename(pkg.orig_tar_gz))
+        os.symlink(pkg.orig_tar_gz, dest)
+
+    # Extract the source
+    cmd = "dpkg-source -sn -x %s" % (dsc_filename)
+    (result, output) = commands.getstatusoutput(cmd)
+    if (result != 0):
+        reject("'dpkg-source -x' failed for %s [return code: %s]." % (dsc_filename, result))
+        reject(utils.prefix_multi_line_string(output, " [dpkg-source output:] "), "")
+        return
+
+    if not Cnf.Find("Dir::Queue::BTSVersionTrack"):
+        return
+
+    # Get the upstream version
+    upstr_version = utils.re_no_epoch.sub('', dsc["version"])
+    if re_strip_revision.search(upstr_version):
+        upstr_version = re_strip_revision.sub('', upstr_version)
+
+    # Ensure the changelog file exists
+    changelog_filename = "%s-%s/debian/changelog" % (dsc["source"], upstr_version)
+    if not os.path.exists(changelog_filename):
+        reject("%s: debian/changelog not found in extracted source." % (dsc_filename))
+        return
+
+    # Parse the changelog
+    dsc["bts changelog"] = ""
+    changelog_file = utils.open_file(changelog_filename)
+    for line in changelog_file.readlines():
+        m = re_changelog_versions.match(line)
+        if m:
+            dsc["bts changelog"] += line
+    changelog_file.close()
+
+    # Check we found at least one revision in the changelog
+    if not dsc["bts changelog"]:
+        reject("%s: changelog format not recognised (empty version tree)." % (dsc_filename))
+
+########################################
+
+def check_source():
+    # Bail out if:
+    #    a) there's no source
+    # or b) reprocess is 2 - we will do this check next time when orig.tar.gz is in 'files'
+    # or c) the orig.tar.gz is MIA
+    if not changes["architecture"].has_key("source") or reprocess == 2 \
+       or pkg.orig_tar_gz == -1:
+        return
+
+    # Create a temporary directory to extract the source into
+    if Options["No-Action"]:
+        tmpdir = tempfile.mkdtemp()
+    else:
+        # We're in queue/holding and can create a random directory.
+        tmpdir = "%s" % (os.getpid())
+        os.mkdir(tmpdir)
+
+    # Move into the temporary directory
+    cwd = os.getcwd()
+    os.chdir(tmpdir)
+
+    # Get the changelog version history
+    get_changelog_versions(cwd)
+
+    # Move back and cleanup the temporary tree
+    os.chdir(cwd)
+    try:
+        shutil.rmtree(tmpdir)
+    except OSError, e:
+        if errno.errorcode[e.errno] != 'EACCES':
+            utils.fubar("%s: couldn't remove tmp dir for source tree." % (dsc["source"]))
+
+        reject("%s: source tree could not be cleanly removed." % (dsc["source"]))
+        # We probably have u-r or u-w directories so chmod everything
+        # and try again.
+        cmd = "chmod -R u+rwx %s" % (tmpdir)
+        result = os.system(cmd)
+        if result != 0:
+            utils.fubar("'%s' failed with result %s." % (cmd, result))
+        shutil.rmtree(tmpdir)
+    except:
+        utils.fubar("%s: couldn't remove tmp dir for source tree." % (dsc["source"]))
+
+################################################################################
+
+# FIXME: should be a debian specific check called from a hook
+
+def check_urgency ():
+    if changes["architecture"].has_key("source"):
+        if not changes.has_key("urgency"):
+            changes["urgency"] = Cnf["Urgency::Default"]
+        changes["urgency"] = changes["urgency"].lower()
+        if changes["urgency"] not in Cnf.ValueList("Urgency::Valid"):
+            reject("%s is not a valid urgency; it will be treated as %s by testing." % (changes["urgency"], Cnf["Urgency::Default"]), "Warning: ")
+            changes["urgency"] = Cnf["Urgency::Default"]
+
+################################################################################
+
+def check_hashes ():
+    utils.check_hash(".changes", files, "md5", apt_pkg.md5sum)
+    utils.check_size(".changes", files)
+    utils.check_hash(".dsc", dsc_files, "md5", apt_pkg.md5sum)
+    utils.check_size(".dsc", dsc_files)
+
+    # This is stupid API, but it'll have to do for now until
+    # we actually have proper abstraction
+    for m in utils.ensure_hashes(changes, dsc, files, dsc_files):
+        reject(m)
+
+################################################################################
+
+# Sanity check the time stamps of files inside debs.
+# [Files in the near future cause ugly warnings and extreme time
+#  travel can cause errors on extraction]
+
+def check_timestamps():
+    class Tar:
+        def __init__(self, future_cutoff, past_cutoff):
+            self.reset()
+            self.future_cutoff = future_cutoff
+            self.past_cutoff = past_cutoff
+
+        def reset(self):
+            self.future_files = {}
+            self.ancient_files = {}
+
+        def callback(self, Kind,Name,Link,Mode,UID,GID,Size,MTime,Major,Minor):
+            if MTime > self.future_cutoff:
+                self.future_files[Name] = MTime
+            if MTime < self.past_cutoff:
+                self.ancient_files[Name] = MTime
+    ####
+
+    future_cutoff = time.time() + int(Cnf["Dinstall::FutureTimeTravelGrace"])
+    past_cutoff = time.mktime(time.strptime(Cnf["Dinstall::PastCutoffYear"],"%Y"))
+    tar = Tar(future_cutoff, past_cutoff)
+    for filename in files.keys():
+        if files[filename]["type"] == "deb":
+            tar.reset()
+            try:
+                deb_file = utils.open_file(filename)
+                apt_inst.debExtract(deb_file,tar.callback,"control.tar.gz")
+                deb_file.seek(0)
+                try:
+                    apt_inst.debExtract(deb_file,tar.callback,"data.tar.gz")
+                except SystemError, e:
+                    # If we can't find a data.tar.gz, look for data.tar.bz2 instead.
+                    if not re.search(r"Cannot f[ui]nd chunk data.tar.gz$", str(e)):
+                        raise
+                    deb_file.seek(0)
+                    apt_inst.debExtract(deb_file,tar.callback,"data.tar.bz2")
+                deb_file.close()
+                #
+                future_files = tar.future_files.keys()
+                if future_files:
+                    num_future_files = len(future_files)
+                    future_file = future_files[0]
+                    future_date = tar.future_files[future_file]
+                    reject("%s: has %s file(s) with a time stamp too far into the future (e.g. %s [%s])."
+                           % (filename, num_future_files, future_file,
+                              time.ctime(future_date)))
+                #
+                ancient_files = tar.ancient_files.keys()
+                if ancient_files:
+                    num_ancient_files = len(ancient_files)
+                    ancient_file = ancient_files[0]
+                    ancient_date = tar.ancient_files[ancient_file]
+                    reject("%s: has %s file(s) with a time stamp too ancient (e.g. %s [%s])."
+                           % (filename, num_ancient_files, ancient_file,
+                              time.ctime(ancient_date)))
+            except:
+                reject("%s: deb contents timestamp check failed [%s: %s]" % (filename, sys.exc_type, sys.exc_value))
+
+################################################################################
+
+def lookup_uid_from_fingerprint(fpr):
+    q = Upload.projectB.query("SELECT u.uid, u.name FROM fingerprint f, uid u WHERE f.uid = u.id AND f.fingerprint = '%s'" % (fpr))
+    qs = q.getresult()
+    if len(qs) == 0:
+        return (None, None)
+    else:
+        return qs[0]
+
+def check_signed_by_key():
+    """Ensure the .changes is signed by an authorized uploader."""
+
+    (uid, uid_name) = lookup_uid_from_fingerprint(changes["fingerprint"])
+    if uid_name == None:
+        uid_name = ""
+
+    # match claimed name with actual name:
+    if uid == None:
+        uid, uid_email = changes["fingerprint"], uid
+        may_nmu, may_sponsor = 1, 1
+        # XXX by default new dds don't have a fingerprint/uid in the db atm,
+        #     and can't get one in there if we don't allow nmu/sponsorship
+    elif uid[:3] == "dm:":
+        uid_email = uid[3:]
+        may_nmu, may_sponsor = 0, 0
+    else:
+        uid_email = "%s@debian.org" % (uid)
+        may_nmu, may_sponsor = 1, 1
+
+    if uid_email in [changes["maintaineremail"], changes["changedbyemail"]]:
+        sponsored = 0
+    elif uid_name in [changes["maintainername"], changes["changedbyname"]]:
+        sponsored = 0
+        if uid_name == "": sponsored = 1
+    else:
+        sponsored = 1
+        if ("source" in changes["architecture"] and
+            uid_email and utils.is_email_alias(uid_email)):
+            sponsor_addresses = utils.gpg_get_key_addresses(changes["fingerprint"])
+            if (changes["maintaineremail"] not in sponsor_addresses and
+                changes["changedbyemail"] not in sponsor_addresses):
+                changes["sponsoremail"] = uid_email
+
+    if sponsored and not may_sponsor:
+        reject("%s is not authorised to sponsor uploads" % (uid))
+
+    if not sponsored and not may_nmu:
+        source_ids = []
+        check_suites = changes["distribution"].keys()
+        if "unstable" not in check_suites: check_suites.append("unstable")
+        for suite in check_suites:
+            suite_id = database.get_suite_id(suite)
+            q = Upload.projectB.query("SELECT s.id FROM source s JOIN src_associations sa ON (s.id = sa.source) WHERE s.source = '%s' AND sa.suite = %d" % (changes["source"], suite_id))
+            for si in q.getresult():
+                if si[0] not in source_ids: source_ids.append(si[0])
+
+        is_nmu = 1
+        for si in source_ids:
+            is_nmu = 1
+            q = Upload.projectB.query("SELECT m.name FROM maintainer m WHERE m.id IN (SELECT maintainer FROM src_uploaders WHERE src_uploaders.source = %s)" % (si))
+            for m in q.getresult():
+                (rfc822, rfc2047, name, email) = utils.fix_maintainer(m[0])
+                if email == uid_email or name == uid_name:
+                    is_nmu=0
+                    break
+        if is_nmu:
+            reject("%s may not upload/NMU source package %s" % (uid, changes["source"]))
+
+        for b in changes["binary"].keys():
+            for suite in changes["distribution"].keys():
+                suite_id = database.get_suite_id(suite)
+                q = Upload.projectB.query("SELECT DISTINCT s.source FROM source s JOIN binaries b ON (s.id = b.source) JOIN bin_associations ba On (b.id = ba.bin) WHERE b.package = '%s' AND ba.suite = %s" % (b, suite_id))
+                for s in q.getresult():
+                    if s[0] != changes["source"]:
+                        reject("%s may not hijack %s from source package %s in suite %s" % (uid, b, s, suite))
+
+        for f in files.keys():
+            if files[f].has_key("byhand"):
+                reject("%s may not upload BYHAND file %s" % (uid, f))
+            if files[f].has_key("new"):
+                reject("%s may not upload NEW file %s" % (uid, f))
+
+
+################################################################################
+################################################################################
+
+# If any file of an upload has a recent mtime then chances are good
+# the file is still being uploaded.
+
+def upload_too_new():
+    too_new = 0
+    # Move back to the original directory to get accurate time stamps
+    cwd = os.getcwd()
+    os.chdir(pkg.directory)
+    file_list = pkg.files.keys()
+    file_list.extend(pkg.dsc_files.keys())
+    file_list.append(pkg.changes_file)
+    for f in file_list:
+        try:
+            last_modified = time.time()-os.path.getmtime(f)
+            if last_modified < int(Cnf["Dinstall::SkipTime"]):
+                too_new = 1
+                break
+        except:
+            pass
+    os.chdir(cwd)
+    return too_new
+
+################################################################################
+
+def action ():
+    # changes["distribution"] may not exist in corner cases
+    # (e.g. unreadable changes files)
+    if not changes.has_key("distribution") or not isinstance(changes["distribution"], DictType):
+        changes["distribution"] = {}
+
+    (summary, short_summary) = Upload.build_summaries()
+
+    # q-unapproved hax0ring
+    queue_info = {
+         "New": { "is": is_new, "process": acknowledge_new },
+         "Autobyhand" : { "is" : is_autobyhand, "process": do_autobyhand },
+         "Byhand" : { "is": is_byhand, "process": do_byhand },
+         "OldStableUpdate" : { "is": is_oldstableupdate,
+                                "process": do_oldstableupdate },
+         "StableUpdate" : { "is": is_stableupdate, "process": do_stableupdate },
+         "Unembargo" : { "is": is_unembargo, "process": queue_unembargo },
+         "Embargo" : { "is": is_embargo, "process": queue_embargo },
+    }
+    queues = [ "New", "Autobyhand", "Byhand" ]
+    if Cnf.FindB("Dinstall::SecurityQueueHandling"):
+        queues += [ "Unembargo", "Embargo" ]
+    else:
+        queues += [ "OldStableUpdate", "StableUpdate" ]
+
+    (prompt, answer) = ("", "XXX")
+    if Options["No-Action"] or Options["Automatic"]:
+        answer = 'S'
+
+    queuekey = ''
+
+    if reject_message.find("Rejected") != -1:
+        if upload_too_new():
+            print "SKIP (too new)\n" + reject_message,
+            prompt = "[S]kip, Quit ?"
+        else:
+            print "REJECT\n" + reject_message,
+            prompt = "[R]eject, Skip, Quit ?"
+            if Options["Automatic"]:
+                answer = 'R'
+    else:
+        qu = None
+        for q in queues:
+            if queue_info[q]["is"]():
+                qu = q
+                break
+        if qu:
+            print "%s for %s\n%s%s" % (
+                qu.upper(), ", ".join(changes["distribution"].keys()),
+                reject_message, summary),
+            queuekey = qu[0].upper()
+            if queuekey in "RQSA":
+                queuekey = "D"
+                prompt = "[D]ivert, Skip, Quit ?"
+            else:
+                prompt = "[%s]%s, Skip, Quit ?" % (queuekey, qu[1:].lower())
+            if Options["Automatic"]:
+                answer = queuekey
+        else:
+            print "ACCEPT\n" + reject_message + summary,
+            prompt = "[A]ccept, Skip, Quit ?"
+            if Options["Automatic"]:
+                answer = 'A'
+
+    while prompt.find(answer) == -1:
+        answer = utils.our_raw_input(prompt)
+        m = queue.re_default_answer.match(prompt)
+        if answer == "":
+            answer = m.group(1)
+        answer = answer[:1].upper()
+
+    if answer == 'R':
+        os.chdir (pkg.directory)
+        Upload.do_reject(0, reject_message)
+    elif answer == 'A':
+        accept(summary, short_summary)
+        remove_from_unchecked()
+    elif answer == queuekey:
+        queue_info[qu]["process"](summary, short_summary)
+        remove_from_unchecked()
+    elif answer == 'Q':
+        sys.exit(0)
+
+def remove_from_unchecked():
+    os.chdir (pkg.directory)
+    for f in files.keys():
+        os.unlink(f)
+    os.unlink(pkg.changes_file)
+
+################################################################################
+
+def accept (summary, short_summary):
+    Upload.accept(summary, short_summary)
+    Upload.check_override()
+
+################################################################################
+
+def move_to_dir (dest, perms=0660, changesperms=0664):
+    utils.move (pkg.changes_file, dest, perms=changesperms)
+    file_keys = files.keys()
+    for f in file_keys:
+        utils.move (f, dest, perms=perms)
+
+################################################################################
+
+def is_unembargo ():
+    q = Upload.projectB.query(
+      "SELECT package FROM disembargo WHERE package = '%s' AND version = '%s'" %
+      (changes["source"], changes["version"]))
+    ql = q.getresult()
+    if ql:
+        return 1
+
+    oldcwd = os.getcwd()
+    os.chdir(Cnf["Dir::Queue::Disembargo"])
+    disdir = os.getcwd()
+    os.chdir(oldcwd)
+
+    if pkg.directory == disdir:
+        if changes["architecture"].has_key("source"):
+            if Options["No-Action"]: return 1
+
+            Upload.projectB.query(
+              "INSERT INTO disembargo (package, version) VALUES ('%s', '%s')" %
+              (changes["source"], changes["version"]))
+            return 1
+
+    return 0
+
+def queue_unembargo (summary, short_summary):
+    print "Moving to UNEMBARGOED holding area."
+    Logger.log(["Moving to unembargoed", pkg.changes_file])
+
+    Upload.dump_vars(Cnf["Dir::Queue::Unembargoed"])
+    move_to_dir(Cnf["Dir::Queue::Unembargoed"])
+    Upload.queue_build("unembargoed", Cnf["Dir::Queue::Unembargoed"])
+
+    # Check for override disparities
+    Upload.Subst["__SUMMARY__"] = summary
+    Upload.check_override()
+
+    # Send accept mail, announce to lists, close bugs and check for
+    # override disparities
+    if not Cnf["Dinstall::Options::No-Mail"]:
+        Upload.Subst["__SUITE__"] = ""
+        mail_message = utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/process-unchecked.accepted")
+        utils.send_mail(mail_message)
+        Upload.announce(short_summary, 1)
+
+################################################################################
+
+def is_embargo ():
+    # if embargoed queues are enabled always embargo
+    return 1
+
+def queue_embargo (summary, short_summary):
+    print "Moving to EMBARGOED holding area."
+    Logger.log(["Moving to embargoed", pkg.changes_file])
+
+    Upload.dump_vars(Cnf["Dir::Queue::Embargoed"])
+    move_to_dir(Cnf["Dir::Queue::Embargoed"])
+    Upload.queue_build("embargoed", Cnf["Dir::Queue::Embargoed"])
+
+    # Check for override disparities
+    Upload.Subst["__SUMMARY__"] = summary
+    Upload.check_override()
+
+    # Send accept mail, announce to lists, close bugs and check for
+    # override disparities
+    if not Cnf["Dinstall::Options::No-Mail"]:
+        Upload.Subst["__SUITE__"] = ""
+        mail_message = utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/process-unchecked.accepted")
+        utils.send_mail(mail_message)
+        Upload.announce(short_summary, 1)
+
+################################################################################
+
+def is_stableupdate ():
+    if not changes["distribution"].has_key("proposed-updates"):
+        return 0
+
+    if not changes["architecture"].has_key("source"):
+        pusuite = database.get_suite_id("proposed-updates")
+        q = Upload.projectB.query(
+          "SELECT S.source FROM source s JOIN src_associations sa ON (s.id = sa.source) WHERE s.source = '%s' AND s.version = '%s' AND sa.suite = %d" %
+          (changes["source"], changes["version"], pusuite))
+        ql = q.getresult()
+        if ql:
+            # source is already in proposed-updates so no need to hold
+            return 0
+
+    return 1
+
+def do_stableupdate (summary, short_summary):
+    print "Moving to PROPOSED-UPDATES holding area."
+    Logger.log(["Moving to proposed-updates", pkg.changes_file]);
+
+    Upload.dump_vars(Cnf["Dir::Queue::ProposedUpdates"]);
+    move_to_dir(Cnf["Dir::Queue::ProposedUpdates"], perms=0664)
+
+    # Check for override disparities
+    Upload.Subst["__SUMMARY__"] = summary;
+    Upload.check_override();
+
+################################################################################
+
+def is_oldstableupdate ():
+    if not changes["distribution"].has_key("oldstable-proposed-updates"):
+        return 0
+
+    if not changes["architecture"].has_key("source"):
+        pusuite = database.get_suite_id("oldstable-proposed-updates")
+        q = Upload.projectB.query(
+          "SELECT S.source FROM source s JOIN src_associations sa ON (s.id = sa.source) WHERE s.source = '%s' AND s.version = '%s' AND sa.suite = %d" %
+          (changes["source"], changes["version"], pusuite))
+        ql = q.getresult()
+        if ql:
+            # source is already in oldstable-proposed-updates so no need to hold
+            return 0
+
+    return 1
+
+def do_oldstableupdate (summary, short_summary):
+    print "Moving to OLDSTABLE-PROPOSED-UPDATES holding area."
+    Logger.log(["Moving to oldstable-proposed-updates", pkg.changes_file]);
+
+    Upload.dump_vars(Cnf["Dir::Queue::OldProposedUpdates"]);
+    move_to_dir(Cnf["Dir::Queue::OldProposedUpdates"], perms=0664)
+
+    # Check for override disparities
+    Upload.Subst["__SUMMARY__"] = summary;
+    Upload.check_override();
+
+################################################################################
+
+def is_autobyhand ():
+    all_auto = 1
+    any_auto = 0
+    for f in files.keys():
+        if files[f].has_key("byhand"):
+            any_auto = 1
+
+            # filename is of form "PKG_VER_ARCH.EXT" where PKG, VER and ARCH
+            # don't contain underscores, and ARCH doesn't contain dots.
+            # further VER matches the .changes Version:, and ARCH should be in
+            # the .changes Architecture: list.
+            if f.count("_") < 2:
+                all_auto = 0
+                continue
+
+            (pckg, ver, archext) = f.split("_", 2)
+            if archext.count(".") < 1 or changes["version"] != ver:
+                all_auto = 0
+                continue
+
+            ABH = Cnf.SubTree("AutomaticByHandPackages")
+            if not ABH.has_key(pckg) or \
+              ABH["%s::Source" % (pckg)] != changes["source"]:
+                print "not match %s %s" % (pckg, changes["source"])
+                all_auto = 0
+                continue
+
+            (arch, ext) = archext.split(".", 1)
+            if arch not in changes["architecture"]:
+                all_auto = 0
+                continue
+
+            files[f]["byhand-arch"] = arch
+            files[f]["byhand-script"] = ABH["%s::Script" % (pckg)]
+
+    return any_auto and all_auto
+
+def do_autobyhand (summary, short_summary):
+    print "Attempting AUTOBYHAND."
+    byhandleft = 0
+    for f in files.keys():
+        byhandfile = f
+        if not files[f].has_key("byhand"):
+            continue
+        if not files[f].has_key("byhand-script"):
+            byhandleft = 1
+            continue
+
+        os.system("ls -l %s" % byhandfile)
+        result = os.system("%s %s %s %s %s" % (
+                files[f]["byhand-script"], byhandfile,
+                changes["version"], files[f]["byhand-arch"],
+                os.path.abspath(pkg.changes_file)))
+        if result == 0:
+            os.unlink(byhandfile)
+            del files[f]
+        else:
+            print "Error processing %s, left as byhand." % (f)
+            byhandleft = 1
+
+    if byhandleft:
+        do_byhand(summary, short_summary)
+    else:
+        accept(summary, short_summary)
+
+################################################################################
+
+def is_byhand ():
+    for f in files.keys():
+        if files[f].has_key("byhand"):
+            return 1
+    return 0
+
+def do_byhand (summary, short_summary):
+    print "Moving to BYHAND holding area."
+    Logger.log(["Moving to byhand", pkg.changes_file])
+
+    Upload.dump_vars(Cnf["Dir::Queue::Byhand"])
+    move_to_dir(Cnf["Dir::Queue::Byhand"])
+
+    # Check for override disparities
+    Upload.Subst["__SUMMARY__"] = summary
+    Upload.check_override()
+
+################################################################################
+
+def is_new ():
+    for f in files.keys():
+        if files[f].has_key("new"):
+            return 1
+    return 0
+
+def acknowledge_new (summary, short_summary):
+    Subst = Upload.Subst
+
+    print "Moving to NEW holding area."
+    Logger.log(["Moving to new", pkg.changes_file])
+
+    Upload.dump_vars(Cnf["Dir::Queue::New"])
+    move_to_dir(Cnf["Dir::Queue::New"])
+
+    if not Options["No-Mail"]:
+        print "Sending new ack."
+        Subst["__SUMMARY__"] = summary
+        new_ack_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.new")
+        utils.send_mail(new_ack_message)
+
+################################################################################
+
+# reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
+# Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
+# Upload.check_dsc_against_db() can find the .orig.tar.gz but it will
+# not have processed it during it's checks of -2.  If -1 has been
+# deleted or otherwise not checked by 'dak process-unchecked', the
+# .orig.tar.gz will not have been checked at all.  To get round this,
+# we force the .orig.tar.gz into the .changes structure and reprocess
+# the .changes file.
+
+def process_it (changes_file):
+    global reprocess, reject_message
+
+    # Reset some globals
+    reprocess = 1
+    Upload.init_vars()
+    # Some defaults in case we can't fully process the .changes file
+    changes["maintainer2047"] = Cnf["Dinstall::MyEmailAddress"]
+    changes["changedby2047"] = Cnf["Dinstall::MyEmailAddress"]
+    reject_message = ""
+
+    # Absolutize the filename to avoid the requirement of being in the
+    # same directory as the .changes file.
+    pkg.changes_file = os.path.abspath(changes_file)
+
+    # Remember where we are so we can come back after cd-ing into the
+    # holding directory.
+    pkg.directory = os.getcwd()
+
+    try:
+        # If this is the Real Thing(tm), copy things into a private
+        # holding directory first to avoid replacable file races.
+        if not Options["No-Action"]:
+            os.chdir(Cnf["Dir::Queue::Holding"])
+            copy_to_holding(pkg.changes_file)
+            # Relativize the filename so we use the copy in holding
+            # rather than the original...
+            pkg.changes_file = os.path.basename(pkg.changes_file)
+        changes["fingerprint"] = utils.check_signature(pkg.changes_file, reject)
+        if changes["fingerprint"]:
+            valid_changes_p = check_changes()
+        else:
+            valid_changes_p = 0
+        if valid_changes_p:
+            while reprocess:
+                check_distributions()
+                check_files()
+                valid_dsc_p = check_dsc()
+                if valid_dsc_p:
+                    check_source()
+                check_hashes()
+                check_urgency()
+                check_timestamps()
+                check_signed_by_key()
+        Upload.update_subst(reject_message)
+        action()
+    except SystemExit:
+        raise
+    except:
+        print "ERROR"
+        traceback.print_exc(file=sys.stderr)
+        pass
+
+    # Restore previous WD
+    os.chdir(pkg.directory)
+
+###############################################################################
+
+def main():
+    global Cnf, Options, Logger
+
+    changes_files = init()
+
+    # -n/--dry-run invalidates some other options which would involve things happening
+    if Options["No-Action"]:
+        Options["Automatic"] = ""
+
+    # Ensure all the arguments we were given are .changes files
+    for f in changes_files:
+        if not f.endswith(".changes"):
+            utils.warn("Ignoring '%s' because it's not a .changes file." % (f))
+            changes_files.remove(f)
+
+    if changes_files == []:
+        utils.fubar("Need at least one .changes file as an argument.")
+
+    # Check that we aren't going to clash with the daily cron job
+
+    if not Options["No-Action"] and os.path.exists("%s/daily.lock" % (Cnf["Dir::Lock"])) and not Options["No-Lock"]:
+        utils.fubar("Archive maintenance in progress.  Try again later.")
+
+    # Obtain lock if not in no-action mode and initialize the log
+
+    if not Options["No-Action"]:
+        lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR | os.O_CREAT)
+        try:
+            fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+        except IOError, e:
+            if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EAGAIN':
+                utils.fubar("Couldn't obtain lock; assuming another 'dak process-unchecked' is already running.")
+            else:
+                raise
+        Logger = Upload.Logger = logging.Logger(Cnf, "process-unchecked")
+
+    # debian-{devel-,}-changes@lists.debian.org toggles writes access based on this header
+    bcc = "X-DAK: dak process-unchecked\nX-Katie: $Revision: 1.65 $"
+    if Cnf.has_key("Dinstall::Bcc"):
+        Upload.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
+    else:
+        Upload.Subst["__BCC__"] = bcc
+
+
+    # Sort the .changes files so that we process sourceful ones first
+    changes_files.sort(utils.changes_compare)
+
+    # Process the changes files
+    for changes_file in changes_files:
+        print "\n" + changes_file
+        try:
+            process_it (changes_file)
+        finally:
+            if not Options["No-Action"]:
+                clean_holding()
+
+    accept_count = Upload.accept_count
+    accept_bytes = Upload.accept_bytes
+    if accept_count:
+        sets = "set"
+        if accept_count > 1:
+            sets = "sets"
+        print "Accepted %d package %s, %s." % (accept_count, sets, utils.size_type(int(accept_bytes)))
+        Logger.log(["total",accept_count,accept_bytes])
+
+    if not Options["No-Action"]:
+        Logger.close()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/queue_report.py b/dak/queue_report.py
new file mode 100755 (executable)
index 0000000..aa1c54d
--- /dev/null
@@ -0,0 +1,460 @@
+#!/usr/bin/env python
+
+# Produces a report on NEW and BYHAND packages
+# Copyright (C) 2001, 2002, 2003, 2005, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <o-o> XP runs GCC, XFREE86, SSH etc etc,.,, I feel almost like linux....
+# <o-o> I am very confident that I can replicate any Linux application on XP
+# <willy> o-o: *boggle*
+# <o-o> building from source.
+# <o-o> Viiru: I already run GIMP under XP
+# <willy> o-o: why do you capitalise the names of all pieces of software?
+# <o-o> willy: because I want the EMPHASIZE them....
+# <o-o> grr s/the/to/
+# <willy> o-o: it makes you look like ZIPPY the PINHEAD
+# <o-o> willy: no idea what you are talking about.
+# <willy> o-o: do some research
+# <o-o> willy: for what reason?
+
+################################################################################
+
+import copy, glob, os, stat, sys, time
+import apt_pkg
+from daklib import queue
+from daklib import utils
+from daklib.dak_exceptions import *
+
+Cnf = None
+Upload = None
+direction = []
+row_number = 0
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak queue-report
+Prints a report of packages in queue directories (usually new and byhand).
+
+  -h, --help                show this help and exit.
+  -n, --new                 produce html-output
+  -s, --sort=key            sort output according to key, see below.
+  -a, --age=key             if using sort by age, how should time be treated?
+                            If not given a default of hours will be used.
+
+     Sorting Keys: ao=age,   oldest first.   an=age,   newest first.
+                   na=name,  ascending       nd=name,  descending
+                   nf=notes, first           nl=notes, last
+
+     Age Keys: m=minutes, h=hours, d=days, w=weeks, o=months, y=years
+
+"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def plural(x):
+    if x > 1:
+        return "s"
+    else:
+        return ""
+
+################################################################################
+
+def time_pp(x):
+    if x < 60:
+        unit="second"
+    elif x < 3600:
+        x /= 60
+        unit="minute"
+    elif x < 86400:
+        x /= 3600
+        unit="hour"
+    elif x < 604800:
+        x /= 86400
+        unit="day"
+    elif x < 2419200:
+        x /= 604800
+        unit="week"
+    elif x < 29030400:
+        x /= 2419200
+        unit="month"
+    else:
+        x /= 29030400
+        unit="year"
+    x = int(x)
+    return "%s %s%s" % (x, unit, plural(x))
+
+################################################################################
+
+def sg_compare (a, b):
+    a = a[1]
+    b = b[1]
+    """Sort by have note, time of oldest upload."""
+    # Sort by have note
+    a_note_state = a["note_state"]
+    b_note_state = b["note_state"]
+    if a_note_state < b_note_state:
+        return -1
+    elif a_note_state > b_note_state:
+        return 1
+
+    # Sort by time of oldest upload
+    return cmp(a["oldest"], b["oldest"])
+
+############################################################
+
+def sortfunc(a,b):
+    for sorting in direction:
+        (sortkey, way, time) = sorting
+        ret = 0
+        if time == "m":
+            x=int(a[sortkey]/60)
+            y=int(b[sortkey]/60)
+        elif time == "h":
+            x=int(a[sortkey]/3600)
+            y=int(b[sortkey]/3600)
+        elif time == "d":
+            x=int(a[sortkey]/86400)
+            y=int(b[sortkey]/86400)
+        elif time == "w":
+            x=int(a[sortkey]/604800)
+            y=int(b[sortkey]/604800)
+        elif time == "o":
+            x=int(a[sortkey]/2419200)
+            y=int(b[sortkey]/2419200)
+        elif time == "y":
+            x=int(a[sortkey]/29030400)
+            y=int(b[sortkey]/29030400)
+        else:
+            x=a[sortkey]
+            y=b[sortkey]
+        if x < y:
+            ret = -1
+        elif x > y:
+            ret = 1
+        if ret != 0:
+            if way < 0:
+                ret = ret*-1
+            return ret
+    return 0
+
+############################################################
+
+def header():
+    print """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+        <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+        <title>Debian NEW and BYHAND Packages</title>
+        <link type="text/css" rel="stylesheet" href="style.css">
+        <link rel="shortcut icon" href="http://www.debian.org/favicon.ico">
+        </head>
+        <body>
+        <div align="center">
+        <a href="http://www.debian.org/">
+     <img src="http://www.debian.org/logos/openlogo-nd-50.png" border="0" hspace="0" vspace="0" alt=""></a>
+        <a href="http://www.debian.org/">
+     <img src="http://www.debian.org/Pics/debian.png" border="0" hspace="0" vspace="0" alt="Debian Project"></a>
+        </div>
+        <br />
+        <table class="reddy" width="100%">
+        <tr>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-upperleft.png" align="left" border="0" hspace="0" vspace="0"
+     alt="" width="15" height="16"></td>
+        <td rowspan="2" class="reddy">Debian NEW and BYHAND Packages</td>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-upperright.png" align="right" border="0" hspace="0" vspace="0"
+     alt="" width="16" height="16"></td>
+        </tr>
+        <tr>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-lowerleft.png" align="left" border="0" hspace="0" vspace="0"
+     alt="" width="16" height="16"></td>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-lowerright.png" align="right" border="0" hspace="0" vspace="0"
+     alt="" width="15" height="16"></td>
+        </tr>
+        </table>
+        """
+
+def footer():
+    print "<p class=\"validate\">Timestamp: %s (UTC)</p>" % (time.strftime("%d.%m.%Y / %H:%M:%S", time.gmtime()))
+    print "<hr><p>Hint: Age is the youngest upload of the package, if there is more than one version.</p>"
+    print "<p>You may want to look at <a href=\"http://ftp-master.debian.org/REJECT-FAQ.html\">the REJECT-FAQ</a> for possible reasons why one of the above packages may get rejected.</p>"
+    print """<a href="http://validator.w3.org/check?uri=referer">
+    <img border="0" src="http://www.w3.org/Icons/valid-html401" alt="Valid HTML 4.01!" height="31" width="88"></a>
+        <a href="http://jigsaw.w3.org/css-validator/check/referer">
+    <img border="0" src="http://jigsaw.w3.org/css-validator/images/vcss" alt="Valid CSS!"
+     height="31" width="88"></a>
+    """
+    print "</body></html>"
+
+def table_header(type):
+    print "<h1>Summary for: %s</h1>" % (type)
+    print """<center><table border="0">
+        <tr>
+          <th align="center">Package</th>
+          <th align="center">Version</th>
+          <th align="center">Arch</th>
+          <th align="center">Distribution</th>
+          <th align="center">Age</th>
+          <th align="center">Maintainer</th>
+          <th align="center">Closes</th>
+        </tr>
+        """
+
+def table_footer(type, source_count, total_count):
+    print "</table></center><br>\n"
+    print "<p class=\"validate\">Package count in <b>%s</b>: <i>%s</i>\n" % (type, source_count)
+    print "<br>Total Package count: <i>%s</i></p>\n" % (total_count)
+
+
+def table_row(source, version, arch, last_mod, maint, distribution, closes):
+
+    global row_number
+
+    if row_number % 2 != 0:
+        print "<tr class=\"even\">"
+    else:
+        print "<tr class=\"odd\">"
+
+    tdclass = "sid"
+    for dist in distribution:
+        if dist == "experimental":
+            tdclass = "exp"
+    print "<td valign=\"top\" class=\"%s\">%s</td>" % (tdclass, source)
+    print "<td valign=\"top\" class=\"%s\">" % (tdclass)
+    for vers in version.split():
+        print "<a href=\"/new/%s_%s.html\">%s</a><br>" % (source, vers, vers)
+    print "</td><td valign=\"top\" class=\"%s\">%s</td><td valign=\"top\" class=\"%s\">" % (tdclass, arch, tdclass)
+    for dist in distribution:
+        print "%s<br>" % (dist)
+    print "</td><td valign=\"top\" class=\"%s\">%s</td>" % (tdclass, last_mod)
+    (name, mail) = maint.split(":")
+
+    print "<td valign=\"top\" class=\"%s\"><a href=\"http://qa.debian.org/developer.php?login=%s\">%s</a></td>" % (tdclass, mail, name)
+    print "<td valign=\"top\" class=\"%s\">" % (tdclass)
+    for close in closes:
+        print "<a href=\"http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s\">#%s</a><br>" % (close, close)
+    print "</td></tr>"
+    row_number+=1
+
+############################################################
+
+def process_changes_files(changes_files, type):
+    msg = ""
+    cache = {}
+    # Read in all the .changes files
+    for filename in changes_files:
+        try:
+            Upload.pkg.changes_file = filename
+            Upload.init_vars()
+            Upload.update_vars()
+            cache[filename] = copy.copy(Upload.pkg.changes)
+            cache[filename]["filename"] = filename
+        except:
+            break
+    # Divide the .changes into per-source groups
+    per_source = {}
+    for filename in cache.keys():
+        source = cache[filename]["source"]
+        if not per_source.has_key(source):
+            per_source[source] = {}
+            per_source[source]["list"] = []
+        per_source[source]["list"].append(cache[filename])
+    # Determine oldest time and have note status for each source group
+    for source in per_source.keys():
+        source_list = per_source[source]["list"]
+        first = source_list[0]
+        oldest = os.stat(first["filename"])[stat.ST_MTIME]
+        have_note = 0
+        for d in per_source[source]["list"]:
+            mtime = os.stat(d["filename"])[stat.ST_MTIME]
+            if Cnf.has_key("Queue-Report::Options::New"):
+                if mtime > oldest:
+                    oldest = mtime
+            else:
+                if mtime < oldest:
+                    oldest = mtime
+            have_note += (d.has_key("process-new note"))
+        per_source[source]["oldest"] = oldest
+        if not have_note:
+            per_source[source]["note_state"] = 0; # none
+        elif have_note < len(source_list):
+            per_source[source]["note_state"] = 1; # some
+        else:
+            per_source[source]["note_state"] = 2; # all
+    per_source_items = per_source.items()
+    per_source_items.sort(sg_compare)
+
+    entries = []
+    max_source_len = 0
+    max_version_len = 0
+    max_arch_len = 0
+    maintainer = {}
+    maint=""
+    distribution=""
+    closes=""
+    for i in per_source_items:
+        last_modified = time.time()-i[1]["oldest"]
+        source = i[1]["list"][0]["source"]
+        if len(source) > max_source_len:
+            max_source_len = len(source)
+        arches = {}
+        versions = {}
+        for j in i[1]["list"]:
+            if Cnf.has_key("Queue-Report::Options::New"):
+                try:
+                    (maintainer["maintainer822"], maintainer["maintainer2047"],
+                    maintainer["maintainername"], maintainer["maintaineremail"]) = \
+                    utils.fix_maintainer (j["maintainer"])
+                except ParseMaintError, msg:
+                    print "Problems while parsing maintainer address\n"
+                    maintainer["maintainername"] = "Unknown"
+                    maintainer["maintaineremail"] = "Unknown"
+                maint="%s:%s" % (maintainer["maintainername"], maintainer["maintaineremail"])
+                distribution=j["distribution"].keys()
+                closes=j["closes"].keys()
+            for arch in j["architecture"].keys():
+                arches[arch] = ""
+            version = j["version"]
+            versions[version] = ""
+        arches_list = arches.keys()
+        arches_list.sort(utils.arch_compare_sw)
+        arch_list = " ".join(arches_list)
+        version_list = " ".join(versions.keys())
+        if len(version_list) > max_version_len:
+            max_version_len = len(version_list)
+        if len(arch_list) > max_arch_len:
+            max_arch_len = len(arch_list)
+        if i[1]["note_state"]:
+            note = " | [N]"
+        else:
+            note = ""
+        entries.append([source, version_list, arch_list, note, last_modified, maint, distribution, closes])
+
+    # direction entry consists of "Which field, which direction, time-consider" where
+    # time-consider says how we should treat last_modified. Thats all.
+
+    # Look for the options for sort and then do the sort.
+    age = "h"
+    if Cnf.has_key("Queue-Report::Options::Age"):
+        age =  Cnf["Queue-Report::Options::Age"]
+    if Cnf.has_key("Queue-Report::Options::New"):
+    # If we produce html we always have oldest first.
+        direction.append([4,-1,"ao"])
+    else:
+        if Cnf.has_key("Queue-Report::Options::Sort"):
+            for i in Cnf["Queue-Report::Options::Sort"].split(","):
+                if i == "ao":
+                    # Age, oldest first.
+                    direction.append([4,-1,age])
+                elif i == "an":
+                    # Age, newest first.
+                    direction.append([4,1,age])
+                elif i == "na":
+                    # Name, Ascending.
+                    direction.append([0,1,0])
+                elif i == "nd":
+                    # Name, Descending.
+                    direction.append([0,-1,0])
+                elif i == "nl":
+                    # Notes last.
+                    direction.append([3,1,0])
+                elif i == "nf":
+                    # Notes first.
+                    direction.append([3,-1,0])
+    entries.sort(lambda x, y: sortfunc(x, y))
+    # Yes, in theory you can add several sort options at the commandline with. But my mind is to small
+    # at the moment to come up with a real good sorting function that considers all the sidesteps you
+    # have with it. (If you combine options it will simply take the last one at the moment).
+    # Will be enhanced in the future.
+
+    if Cnf.has_key("Queue-Report::Options::New"):
+        direction.append([4,1,"ao"])
+        entries.sort(lambda x, y: sortfunc(x, y))
+    # Output for a html file. First table header. then table_footer.
+    # Any line between them is then a <tr> printed from subroutine table_row.
+        if len(entries) > 0:
+            table_header(type.upper())
+            for entry in entries:
+                (source, version_list, arch_list, note, last_modified, maint, distribution, closes) = entry
+                table_row(source, version_list, arch_list, time_pp(last_modified), maint, distribution, closes)
+            total_count = len(changes_files)
+            source_count = len(per_source_items)
+            table_footer(type.upper(), source_count, total_count)
+    else:
+    # The "normal" output without any formatting.
+        format="%%-%ds | %%-%ds | %%-%ds%%s | %%s old\n" % (max_source_len, max_version_len, max_arch_len)
+
+        msg = ""
+        for entry in entries:
+            (source, version_list, arch_list, note, last_modified, undef, undef, undef) = entry
+            msg += format % (source, version_list, arch_list, note, time_pp(last_modified))
+
+        if msg:
+            total_count = len(changes_files)
+            source_count = len(per_source_items)
+            print type.upper()
+            print "-"*len(type)
+            print
+            print msg
+            print "%s %s source package%s / %s %s package%s in total." % (source_count, type, plural(source_count), total_count, type, plural(total_count))
+            print
+
+
+################################################################################
+
+def main():
+    global Cnf, Upload
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Queue-Report::Options::Help"),
+                 ('n',"new","Queue-Report::Options::New"),
+                 ('s',"sort","Queue-Report::Options::Sort", "HasArg"),
+                 ('a',"age","Queue-Report::Options::Age", "HasArg")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Queue-Report::Options::%s" % (i)):
+            Cnf["Queue-Report::Options::%s" % (i)] = ""
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Queue-Report::Options")
+    if Options["Help"]:
+        usage()
+
+    Upload = queue.Upload(Cnf)
+
+    if Cnf.has_key("Queue-Report::Options::New"):
+        header()
+
+    directories = Cnf.ValueList("Queue-Report::Directories")
+    if not directories:
+        directories = [ "byhand", "new" ]
+
+    for directory in directories:
+        changes_files = glob.glob("%s/*.changes" % (Cnf["Dir::Queue::%s" % (directory)]))
+        process_changes_files(changes_files, directory)
+
+    if Cnf.has_key("Queue-Report::Options::New"):
+        footer()
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/reject_proposed_updates.py b/dak/reject_proposed_updates.py
new file mode 100755 (executable)
index 0000000..a61db17
--- /dev/null
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+
+# Manually reject packages for proprosed-updates
+# Copyright (C) 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, pg, sys
+import apt_pkg
+from daklib import database
+from daklib import logging
+from daklib import queue
+from daklib import utils
+
+################################################################################
+
+# Globals
+Cnf = None
+Options = None
+projectB = None
+Upload = None
+Logger = None
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak reject-proposed-updates .CHANGES[...]
+Manually reject the .CHANGES file(s).
+
+  -h, --help                show this help and exit.
+  -m, --message=MSG         use this message for the rejection.
+  -s, --no-mail             don't send any mail."""
+    sys.exit(exit_code)
+
+################################################################################
+
+def main():
+    global Cnf, Logger, Options, projectB, Upload
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Reject-Proposed-Updates::Options::Help"),
+                 ('m',"manual-reject","Reject-Proposed-Updates::Options::Manual-Reject", "HasArg"),
+                 ('s',"no-mail", "Reject-Proposed-Updates::Options::No-Mail")]
+    for i in [ "help", "manual-reject", "no-mail" ]:
+        if not Cnf.has_key("Reject-Proposed-Updates::Options::%s" % (i)):
+            Cnf["Reject-Proposed-Updates::Options::%s" % (i)] = ""
+
+    arguments = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Reject-Proposed-Updates::Options")
+    if Options["Help"]:
+        usage()
+    if not arguments:
+        utils.fubar("need at least one .changes filename as an argument.")
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    Upload = queue.Upload(Cnf)
+    Logger = Upload.Logger = logging.Logger(Cnf, "reject-proposed-updates")
+
+    bcc = "X-DAK: dak rejected-proposed-updates\nX-Katie: lauren $Revision: 1.4 $"
+    if Cnf.has_key("Dinstall::Bcc"):
+        Upload.Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"])
+    else:
+        Upload.Subst["__BCC__"] = bcc
+
+    for arg in arguments:
+        arg = utils.validate_changes_file_arg(arg)
+        Upload.pkg.changes_file = arg
+        Upload.init_vars()
+        cwd = os.getcwd()
+        os.chdir(Cnf["Suite::Proposed-Updates::CopyDotDak"])
+        Upload.update_vars()
+        os.chdir(cwd)
+        Upload.update_subst()
+
+        print arg
+        done = 0
+        prompt = "Manual reject, [S]kip, Quit ?"
+        while not done:
+            answer = "XXX"
+
+            while prompt.find(answer) == -1:
+                answer = utils.our_raw_input(prompt)
+                m = queue.re_default_answer.search(prompt)
+                if answer == "":
+                    answer = m.group(1)
+                answer = answer[:1].upper()
+
+            if answer == 'M':
+                aborted = reject(Options["Manual-Reject"])
+                if not aborted:
+                    done = 1
+            elif answer == 'S':
+                done = 1
+            elif answer == 'Q':
+                sys.exit(0)
+
+    Logger.close()
+
+################################################################################
+
+def reject (reject_message = ""):
+    files = Upload.pkg.files
+    dsc = Upload.pkg.dsc
+    changes_file = Upload.pkg.changes_file
+
+    # If we weren't given a manual rejection message, spawn an editor
+    # so the user can add one in...
+    if not reject_message:
+        temp_filename = utils.temp_filename()
+        editor = os.environ.get("EDITOR","vi")
+        answer = 'E'
+        while answer == 'E':
+            os.system("%s %s" % (editor, temp_filename))
+            f = utils.open_file(temp_filename)
+            reject_message = "".join(f.readlines())
+            f.close()
+            print "Reject message:"
+            print utils.prefix_multi_line_string(reject_message,"  ", include_blank_lines=1)
+            prompt = "[R]eject, Edit, Abandon, Quit ?"
+            answer = "XXX"
+            while prompt.find(answer) == -1:
+                answer = utils.our_raw_input(prompt)
+                m = queue.re_default_answer.search(prompt)
+                if answer == "":
+                    answer = m.group(1)
+                answer = answer[:1].upper()
+        os.unlink(temp_filename)
+        if answer == 'A':
+            return 1
+        elif answer == 'Q':
+            sys.exit(0)
+
+    print "Rejecting.\n"
+
+    # Reject the .changes file
+    Upload.force_reject([changes_file])
+
+    # Setup the .reason file
+    reason_filename = changes_file[:-8] + ".reason"
+    reject_filename = Cnf["Dir::Queue::Reject"] + '/' + reason_filename
+
+    # If we fail here someone is probably trying to exploit the race
+    # so let's just raise an exception ...
+    if os.path.exists(reject_filename):
+        os.unlink(reject_filename)
+    reject_fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
+
+    # Build up the rejection email
+    user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"])
+
+    Upload.Subst["__REJECTOR_ADDRESS__"] = user_email_address
+    Upload.Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message
+    Upload.Subst["__STABLE_REJECTOR__"] = Cnf["Reject-Proposed-Updates::StableRejector"]
+    Upload.Subst["__STABLE_MAIL__"] = Cnf["Reject-Proposed-Updates::StableMail"]
+    Upload.Subst["__MORE_INFO_URL__"] = Cnf["Reject-Proposed-Updates::MoreInfoURL"]
+    Upload.Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
+    reject_mail_message = utils.TemplateSubst(Upload.Subst,Cnf["Dir::Templates"]+"/reject-proposed-updates.rejected")
+
+    # Write the rejection email out as the <foo>.reason file
+    os.write(reject_fd, reject_mail_message)
+    os.close(reject_fd)
+
+    # Remove the packages from proposed-updates
+    suite_id = database.get_suite_id('proposed-updates')
+
+    projectB.query("BEGIN WORK")
+    # Remove files from proposed-updates suite
+    for f in files.keys():
+        if files[f]["type"] == "dsc":
+            package = dsc["source"]
+            version = dsc["version"];  # NB: not files[f]["version"], that has no epoch
+            q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
+            ql = q.getresult()
+            if not ql:
+                utils.fubar("reject: Couldn't find %s_%s in source table." % (package, version))
+            source_id = ql[0][0]
+            projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id))
+        elif files[f]["type"] == "deb":
+            package = files[f]["package"]
+            version = files[f]["version"]
+            architecture = files[f]["architecture"]
+            q = projectB.query("SELECT b.id FROM binaries b, architecture a WHERE b.package = '%s' AND b.version = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all') AND b.architecture = a.id" % (package, version, architecture))
+            ql = q.getresult()
+
+            # Horrible hack to work around partial replacement of
+            # packages with newer versions (from different source
+            # packages).  This, obviously, should instead check for a
+            # newer version of the package and only do the
+            # warn&continue thing if it finds one.
+            if not ql:
+                utils.warn("reject: Couldn't find %s_%s_%s in binaries table." % (package, version, architecture))
+            else:
+                binary_id = ql[0][0]
+                projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id))
+    projectB.query("COMMIT WORK")
+
+    # Send the rejection mail if appropriate
+    if not Options["No-Mail"]:
+        utils.send_mail(reject_mail_message)
+
+    # Finally remove the .dak file
+    dot_dak_file = os.path.join(Cnf["Suite::Proposed-Updates::CopyDotDak"], os.path.basename(changes_file[:-8]+".dak"))
+    os.unlink(dot_dak_file)
+
+    Logger.log(["rejected", changes_file])
+    return 0
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/rm.py b/dak/rm.py
new file mode 100755 (executable)
index 0000000..7535562
--- /dev/null
+++ b/dak/rm.py
@@ -0,0 +1,537 @@
+#!/usr/bin/env python
+
+# General purpose package removal tool for ftpmaster
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# o OpenBSD team wants to get changes incorporated into IPF. Darren no
+#    respond.
+# o Ask again -> No respond. Darren coder supreme.
+# o OpenBSD decide to make changes, but only in OpenBSD source
+#    tree. Darren hears, gets angry! Decides: "LICENSE NO ALLOW!"
+# o Insert Flame War.
+# o OpenBSD team decide to switch to different packet filter under BSD
+#    license. Because Project Goal: Every user should be able to make
+#    changes to source tree. IPF license bad!!
+# o Darren try get back: says, NetBSD, FreeBSD allowed! MUAHAHAHAH!!!
+# o Theo say: no care, pf much better than ipf!
+# o Darren changes mind: changes license. But OpenBSD will not change
+#    back to ipf. Darren even much more bitter.
+# o Darren so bitterbitter. Decides: I'LL GET BACK BY FORKING OPENBSD AND
+#    RELEASING MY OWN VERSION. HEHEHEHEHE.
+
+#                        http://slashdot.org/comments.pl?sid=26697&cid=2883271
+
+################################################################################
+
+import commands, os, pg, re, sys
+import apt_pkg, apt_inst
+from daklib import database
+from daklib import utils
+from daklib.dak_exceptions import *
+
+################################################################################
+
+re_strip_source_version = re.compile (r'\s+.*$')
+re_build_dep_arch = re.compile(r"\[[^]]+\]")
+
+################################################################################
+
+Cnf = None
+Options = None
+projectB = None
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak rm [OPTIONS] PACKAGE[...]
+Remove PACKAGE(s) from suite(s).
+
+  -a, --architecture=ARCH    only act on this architecture
+  -b, --binary               remove binaries only
+  -c, --component=COMPONENT  act on this component
+  -C, --carbon-copy=EMAIL    send a CC of removal message to EMAIL
+  -d, --done=BUG#            send removal message as closure to bug#
+  -h, --help                 show this help and exit
+  -m, --reason=MSG           reason for removal
+  -n, --no-action            don't do anything
+  -p, --partial              don't affect override files
+  -R, --rdep-check           check reverse dependencies
+  -s, --suite=SUITE          act on this suite
+  -S, --source-only          remove source only
+
+ARCH, BUG#, COMPONENT and SUITE can be comma (or space) separated lists, e.g.
+    --architecture=m68k,i386"""
+
+    sys.exit(exit_code)
+
+################################################################################
+
+# "Hudson: What that's great, that's just fucking great man, now what
+#  the fuck are we supposed to do? We're in some real pretty shit now
+#  man...That's it man, game over man, game over, man! Game over! What
+#  the fuck are we gonna do now? What are we gonna do?"
+
+def game_over():
+    answer = utils.our_raw_input("Continue (y/N)? ").lower()
+    if answer != "y":
+        print "Aborted."
+        sys.exit(1)
+
+################################################################################
+
+def reverse_depends_check(removals, suites):
+    print "Checking reverse dependencies..."
+    components = Cnf.ValueList("Suite::%s::Components" % suites[0])
+    dep_problem = 0
+    p2c = {}
+    for architecture in Cnf.ValueList("Suite::%s::Architectures" % suites[0]):
+        if architecture in ["source", "all"]:
+            continue
+        deps = {}
+        virtual_packages = {}
+        for component in components:
+            filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (Cnf["Dir::Root"], suites[0], component, architecture)
+            # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
+            temp_filename = utils.temp_filename()
+            (result, output) = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
+            if (result != 0):
+                utils.fubar("Gunzip invocation failed!\n%s\n" % (output), result)
+            packages = utils.open_file(temp_filename)
+            Packages = apt_pkg.ParseTagFile(packages)
+            while Packages.Step():
+                package = Packages.Section.Find("Package")
+                depends = Packages.Section.Find("Depends")
+                if depends:
+                    deps[package] = depends
+                provides = Packages.Section.Find("Provides")
+                # Maintain a counter for each virtual package.  If a
+                # Provides: exists, set the counter to 0 and count all
+                # provides by a package not in the list for removal.
+                # If the counter stays 0 at the end, we know that only
+                # the to-be-removed packages provided this virtual
+                # package.
+                if provides:
+                    for virtual_pkg in provides.split(","):
+                        virtual_pkg = virtual_pkg.strip()
+                        if virtual_pkg == package: continue
+                        if not virtual_packages.has_key(virtual_pkg):
+                            virtual_packages[virtual_pkg] = 0
+                        if package not in removals:
+                            virtual_packages[virtual_pkg] += 1
+                p2c[package] = component
+            packages.close()
+            os.unlink(temp_filename)
+
+        # If a virtual package is only provided by the to-be-removed
+        # packages, treat the virtual package as to-be-removed too.
+        for virtual_pkg in virtual_packages.keys():
+            if virtual_packages[virtual_pkg] == 0:
+                removals.append(virtual_pkg)
+
+        # Check binary dependencies (Depends)
+        for package in deps.keys():
+            if package in removals: continue
+            parsed_dep = []
+            try:
+                parsed_dep += apt_pkg.ParseDepends(deps[package])
+            except ValueError, e:
+                print "Error for package %s: %s" % (package, e)
+            for dep in parsed_dep:
+                # Check for partial breakage.  If a package has a ORed
+                # dependency, there is only a dependency problem if all
+                # packages in the ORed depends will be removed.
+                unsat = 0
+                for dep_package, _, _ in dep:
+                    if dep_package in removals:
+                        unsat += 1
+                if unsat == len(dep):
+                    component = p2c[package]
+                    if component != "main":
+                        what = "%s/%s" % (package, component)
+                    else:
+                        what = "** %s" % (package)
+                    print "%s has an unsatisfied dependency on %s: %s" % (what, architecture, utils.pp_deps(dep))
+                    dep_problem = 1
+
+    # Check source dependencies (Build-Depends and Build-Depends-Indep)
+    for component in components:
+        filename = "%s/dists/%s/%s/source/Sources.gz" % (Cnf["Dir::Root"], suites[0], component)
+        # apt_pkg.ParseTagFile needs a real file handle and can't handle a GzipFile instance...
+        temp_filename = utils.temp_filename()
+        result, output = commands.getstatusoutput("gunzip -c %s > %s" % (filename, temp_filename))
+        if result != 0:
+            sys.stderr.write("Gunzip invocation failed!\n%s\n" % (output))
+            sys.exit(result)
+        sources = utils.open_file(temp_filename, "r")
+        Sources = apt_pkg.ParseTagFile(sources)
+        while Sources.Step():
+            source = Sources.Section.Find("Package")
+            if source in removals: continue
+            parsed_dep = []
+            for build_dep_type in ["Build-Depends", "Build-Depends-Indep"]:
+                build_dep = Sources.Section.get(build_dep_type)
+                if build_dep:
+                    # Remove [arch] information since we want to see breakage on all arches
+                    build_dep = re_build_dep_arch.sub("", build_dep)
+                    try:
+                        parsed_dep += apt_pkg.ParseDepends(build_dep)
+                    except ValueError, e:
+                        print "Error for source %s: %s" % (source, e)
+            for dep in parsed_dep:
+                unsat = 0
+                for dep_package, _, _ in dep:
+                    if dep_package in removals:
+                        unsat += 1
+                if unsat == len(dep):
+                    if component != "main":
+                        source = "%s/%s" % (source, component)
+                    else:
+                        source = "** %s" % (source)
+                    print "%s has an unsatisfied build-dependency: %s" % (source, utils.pp_deps(dep))
+                    dep_problem = 1
+        sources.close()
+        os.unlink(temp_filename)
+
+    if dep_problem:
+        print "Dependency problem found."
+        if not Options["No-Action"]:
+            game_over()
+    else:
+        print "No dependency problem found."
+    print
+
+################################################################################
+
+def main ():
+    global Cnf, Options, projectB
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Rm::Options::Help"),
+                 ('a',"architecture","Rm::Options::Architecture", "HasArg"),
+                 ('b',"binary", "Rm::Options::Binary-Only"),
+                 ('c',"component", "Rm::Options::Component", "HasArg"),
+                 ('C',"carbon-copy", "Rm::Options::Carbon-Copy", "HasArg"), # Bugs to Cc
+                 ('d',"done","Rm::Options::Done", "HasArg"), # Bugs fixed
+                 ('R',"rdep-check", "Rm::Options::Rdep-Check"),
+                 ('m',"reason", "Rm::Options::Reason", "HasArg"), # Hysterical raisins; -m is old-dinstall option for rejection reason
+                 ('n',"no-action","Rm::Options::No-Action"),
+                 ('p',"partial", "Rm::Options::Partial"),
+                 ('s',"suite","Rm::Options::Suite", "HasArg"),
+                 ('S',"source-only", "Rm::Options::Source-Only"),
+                 ]
+
+    for i in [ "architecture", "binary-only", "carbon-copy", "component",
+               "done", "help", "no-action", "partial", "rdep-check", "reason",
+               "source-only" ]:
+        if not Cnf.has_key("Rm::Options::%s" % (i)):
+            Cnf["Rm::Options::%s" % (i)] = ""
+    if not Cnf.has_key("Rm::Options::Suite"):
+        Cnf["Rm::Options::Suite"] = "unstable"
+
+    arguments = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Rm::Options")
+
+    if Options["Help"]:
+        usage()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+    # Sanity check options
+    if not arguments:
+        utils.fubar("need at least one package name as an argument.")
+    if Options["Architecture"] and Options["Source-Only"]:
+        utils.fubar("can't use -a/--architecutre and -S/--source-only options simultaneously.")
+    if Options["Binary-Only"] and Options["Source-Only"]:
+        utils.fubar("can't use -b/--binary-only and -S/--source-only options simultaneously.")
+    if Options.has_key("Carbon-Copy") and not Options.has_key("Done"):
+        utils.fubar("can't use -C/--carbon-copy without also using -d/--done option.")
+    if Options["Architecture"] and not Options["Partial"]:
+        utils.warn("-a/--architecture implies -p/--partial.")
+        Options["Partial"] = "true"
+
+    # Force the admin to tell someone if we're not doing a 'dak
+    # cruft-report' inspired removal (or closing a bug, which counts
+    # as telling someone).
+    if not Options["No-Action"] and not Options["Carbon-Copy"] \
+           and not Options["Done"] and Options["Reason"].find("[auto-cruft]") == -1:
+        utils.fubar("Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal.")
+
+    # Process -C/--carbon-copy
+    #
+    # Accept 3 types of arguments (space separated):
+    #  1) a number - assumed to be a bug number, i.e. nnnnn@bugs.debian.org
+    #  2) the keyword 'package' - cc's $package@packages.debian.org for every argument
+    #  3) contains a '@' - assumed to be an email address, used unmofidied
+    #
+    carbon_copy = []
+    for copy_to in utils.split_args(Options.get("Carbon-Copy")):
+        if copy_to.isdigit():
+            carbon_copy.append(copy_to + "@" + Cnf["Dinstall::BugServer"])
+        elif copy_to == 'package':
+            for package in arguments:
+                carbon_copy.append(package + "@" + Cnf["Dinstall::PackagesServer"])
+                if Cnf.has_key("Dinstall::TrackingServer"):
+                    carbon_copy.append(package + "@" + Cnf["Dinstall::TrackingServer"])
+        elif '@' in copy_to:
+            carbon_copy.append(copy_to)
+        else:
+            utils.fubar("Invalid -C/--carbon-copy argument '%s'; not a bug number, 'package' or email address." % (copy_to))
+
+    if Options["Binary-Only"]:
+        field = "b.package"
+    else:
+        field = "s.source"
+    con_packages = "AND %s IN (%s)" % (field, ", ".join([ repr(i) for i in arguments ]))
+
+    (con_suites, con_architectures, con_components, check_source) = \
+                 utils.parse_args(Options)
+
+    # Additional suite checks
+    suite_ids_list = []
+    suites = utils.split_args(Options["Suite"])
+    suites_list = utils.join_with_commas_and(suites)
+    if not Options["No-Action"]:
+        for suite in suites:
+            suite_id = database.get_suite_id(suite)
+            if suite_id != -1:
+                suite_ids_list.append(suite_id)
+            if suite == "stable":
+                print "**WARNING** About to remove from the stable suite!"
+                print "This should only be done just prior to a (point) release and not at"
+                print "any other time."
+                game_over()
+            elif suite == "testing":
+                print "**WARNING About to remove from the testing suite!"
+                print "There's no need to do this normally as removals from unstable will"
+                print "propogate to testing automagically."
+                game_over()
+
+    # Additional architecture checks
+    if Options["Architecture"] and check_source:
+        utils.warn("'source' in -a/--argument makes no sense and is ignored.")
+
+    # Additional component processing
+    over_con_components = con_components.replace("c.id", "component")
+
+    print "Working...",
+    sys.stdout.flush()
+    to_remove = []
+    maintainers = {}
+
+    # We have 3 modes of package selection: binary-only, source-only
+    # and source+binary.  The first two are trivial and obvious; the
+    # latter is a nasty mess, but very nice from a UI perspective so
+    # we try to support it.
+
+    if Options["Binary-Only"]:
+        # Binary-only
+        q = projectB.query("SELECT b.package, b.version, a.arch_string, b.id, b.maintainer FROM binaries b, bin_associations ba, architecture a, suite su, files f, location l, component c WHERE ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id AND b.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s %s" % (con_packages, con_suites, con_components, con_architectures))
+        for i in q.getresult():
+            to_remove.append(i)
+    else:
+        # Source-only
+        source_packages = {}
+        q = projectB.query("SELECT l.path, f.filename, s.source, s.version, 'source', s.id, s.maintainer FROM source s, src_associations sa, suite su, files f, location l, component c WHERE sa.source = s.id AND sa.suite = su.id AND s.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s" % (con_packages, con_suites, con_components))
+        for i in q.getresult():
+            source_packages[i[2]] = i[:2]
+            to_remove.append(i[2:])
+        if not Options["Source-Only"]:
+            # Source + Binary
+            binary_packages = {}
+            # First get a list of binary package names we suspect are linked to the source
+            q = projectB.query("SELECT DISTINCT b.package FROM binaries b, source s, src_associations sa, suite su, files f, location l, component c WHERE b.source = s.id AND sa.source = s.id AND sa.suite = su.id AND s.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s" % (con_packages, con_suites, con_components))
+            for i in q.getresult():
+                binary_packages[i[0]] = ""
+            # Then parse each .dsc that we found earlier to see what binary packages it thinks it produces
+            for i in source_packages.keys():
+                filename = "/".join(source_packages[i])
+                try:
+                    dsc = utils.parse_changes(filename)
+                except CantOpenError:
+                    utils.warn("couldn't open '%s'." % (filename))
+                    continue
+                for package in dsc.get("binary").split(','):
+                    package = package.strip()
+                    binary_packages[package] = ""
+            # Then for each binary package: find any version in
+            # unstable, check the Source: field in the deb matches our
+            # source package and if so add it to the list of packages
+            # to be removed.
+            for package in binary_packages.keys():
+                q = projectB.query("SELECT l.path, f.filename, b.package, b.version, a.arch_string, b.id, b.maintainer FROM binaries b, bin_associations ba, architecture a, suite su, files f, location l, component c WHERE ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id AND b.file = f.id AND f.location = l.id AND l.component = c.id %s %s %s AND b.package = '%s'" % (con_suites, con_components, con_architectures, package))
+                for i in q.getresult():
+                    filename = "/".join(i[:2])
+                    control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(filename)))
+                    source = control.Find("Source", control.Find("Package"))
+                    source = re_strip_source_version.sub('', source)
+                    if source_packages.has_key(source):
+                        to_remove.append(i[2:])
+    print "done."
+
+    if not to_remove:
+        print "Nothing to do."
+        sys.exit(0)
+
+    # If we don't have a reason; spawn an editor so the user can add one
+    # Write the rejection email out as the <foo>.reason file
+    if not Options["Reason"] and not Options["No-Action"]:
+        temp_filename = utils.temp_filename()
+        editor = os.environ.get("EDITOR","vi")
+        result = os.system("%s %s" % (editor, temp_filename))
+        if result != 0:
+            utils.fubar ("vi invocation failed for `%s'!" % (temp_filename), result)
+        temp_file = utils.open_file(temp_filename)
+        for line in temp_file.readlines():
+            Options["Reason"] += line
+        temp_file.close()
+        os.unlink(temp_filename)
+
+    # Generate the summary of what's to be removed
+    d = {}
+    for i in to_remove:
+        package = i[0]
+        version = i[1]
+        architecture = i[2]
+        maintainer = i[4]
+        maintainers[maintainer] = ""
+        if not d.has_key(package):
+            d[package] = {}
+        if not d[package].has_key(version):
+            d[package][version] = []
+        if architecture not in d[package][version]:
+            d[package][version].append(architecture)
+
+    maintainer_list = []
+    for maintainer_id in maintainers.keys():
+        maintainer_list.append(database.get_maintainer(maintainer_id))
+    summary = ""
+    removals = d.keys()
+    removals.sort()
+    for package in removals:
+        versions = d[package].keys()
+        versions.sort(apt_pkg.VersionCompare)
+        for version in versions:
+            d[package][version].sort(utils.arch_compare_sw)
+            summary += "%10s | %10s | %s\n" % (package, version, ", ".join(d[package][version]))
+    print "Will remove the following packages from %s:" % (suites_list)
+    print
+    print summary
+    print "Maintainer: %s" % ", ".join(maintainer_list)
+    if Options["Done"]:
+        print "Will also close bugs: "+Options["Done"]
+    if carbon_copy:
+        print "Will also send CCs to: " + ", ".join(carbon_copy)
+    print
+    print "------------------- Reason -------------------"
+    print Options["Reason"]
+    print "----------------------------------------------"
+    print
+
+    if Options["Rdep-Check"]:
+        reverse_depends_check(removals, suites)
+
+    # If -n/--no-action, drop out here
+    if Options["No-Action"]:
+        sys.exit(0)
+
+    print "Going to remove the packages now."
+    game_over()
+
+    whoami = utils.whoami()
+    date = commands.getoutput('date -R')
+
+    # Log first; if it all falls apart I want a record that we at least tried.
+    logfile = utils.open_file(Cnf["Rm::LogFile"], 'a')
+    logfile.write("=========================================================================\n")
+    logfile.write("[Date: %s] [ftpmaster: %s]\n" % (date, whoami))
+    logfile.write("Removed the following packages from %s:\n\n%s" % (suites_list, summary))
+    if Options["Done"]:
+        logfile.write("Closed bugs: %s\n" % (Options["Done"]))
+    logfile.write("\n------------------- Reason -------------------\n%s\n" % (Options["Reason"]))
+    logfile.write("----------------------------------------------\n")
+    logfile.flush()
+
+    dsc_type_id = database.get_override_type_id('dsc')
+    deb_type_id = database.get_override_type_id('deb')
+
+    # Do the actual deletion
+    print "Deleting...",
+    sys.stdout.flush()
+    projectB.query("BEGIN WORK")
+    for i in to_remove:
+        package = i[0]
+        architecture = i[2]
+        package_id = i[3]
+        for suite_id in suite_ids_list:
+            if architecture == "source":
+                projectB.query("DELETE FROM src_associations WHERE source = %s AND suite = %s" % (package_id, suite_id))
+                #print "DELETE FROM src_associations WHERE source = %s AND suite = %s" % (package_id, suite_id)
+            else:
+                projectB.query("DELETE FROM bin_associations WHERE bin = %s AND suite = %s" % (package_id, suite_id))
+                #print "DELETE FROM bin_associations WHERE bin = %s AND suite = %s" % (package_id, suite_id)
+            # Delete from the override file
+            if not Options["Partial"]:
+                if architecture == "source":
+                    type_id = dsc_type_id
+                else:
+                    type_id = deb_type_id
+                projectB.query("DELETE FROM override WHERE package = '%s' AND type = %s AND suite = %s %s" % (package, type_id, suite_id, over_con_components))
+    projectB.query("COMMIT WORK")
+    print "done."
+
+    # Send the bug closing messages
+    if Options["Done"]:
+        Subst = {}
+        Subst["__RM_ADDRESS__"] = Cnf["Rm::MyEmailAddress"]
+        Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]
+        bcc = []
+        if Cnf.Find("Dinstall::Bcc") != "":
+            bcc.append(Cnf["Dinstall::Bcc"])
+        if Cnf.Find("Rm::Bcc") != "":
+            bcc.append(Cnf["Rm::Bcc"])
+        if bcc:
+            Subst["__BCC__"] = "Bcc: " + ", ".join(bcc)
+        else:
+            Subst["__BCC__"] = "X-Filler: 42"
+        Subst["__CC__"] = "X-DAK: dak rm\nX-Katie: melanie"
+        if carbon_copy:
+            Subst["__CC__"] += "\nCc: " + ", ".join(carbon_copy)
+        Subst["__SUITE_LIST__"] = suites_list
+        Subst["__SUMMARY__"] = summary
+        Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
+        Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
+        Subst["__WHOAMI__"] = whoami
+        whereami = utils.where_am_i()
+        Archive = Cnf.SubTree("Archive::%s" % (whereami))
+        Subst["__MASTER_ARCHIVE__"] = Archive["OriginServer"]
+        Subst["__PRIMARY_MIRROR__"] = Archive["PrimaryMirror"]
+        for bug in utils.split_args(Options["Done"]):
+            Subst["__BUG_NUMBER__"] = bug
+            mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/rm.bug-close")
+            utils.send_mail(mail_message)
+
+    logfile.write("=========================================================================\n")
+    logfile.close()
+
+#######################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/show_deferred.py b/dak/show_deferred.py
new file mode 100755 (executable)
index 0000000..833cad2
--- /dev/null
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+
+# based on queue-report
+#    Copyright (C) 2001, 2002, 2003, 2005, 2006  James Troup <james@nocrew.org>
+# Copyright (C) 2008 Thomas Viehmann <tv@beamnet.de>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import sys, os, re, time
+import apt_pkg
+import tempfile
+from debian_bundle import deb822
+from daklib import database
+from daklib import queue
+from daklib import utils
+
+################################################################################
+### work around bug #487902 in debian-python 0.1.10
+deb822.Changes._multivalued_fields = {
+            "files": [ "md5sum", "size", "section", "priority", "name" ],
+            "checksums-sha1": ["sha1", "size", "name"],
+            "checksums-sha256": ["sha256", "size", "name"],
+          }
+
+################################################################################
+
+row_number = 1
+
+html_escaping = {'"':'&quot;', '&':'&amp;', '<':'&lt;', '>':'&gt;'}
+re_html_escaping = re.compile('|'.join(map(re.escape, html_escaping.keys())))
+def html_escape(s):
+    return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)
+
+################################################################################
+
+def header():
+  return  """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+        <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+        <title>Deferred uploads to Debian</title>
+        <link type="text/css" rel="stylesheet" href="style.css">
+        <link rel="shortcut icon" href="http://www.debian.org/favicon.ico">
+        </head>
+        <body>
+        <div align="center">
+        <a href="http://www.debian.org/">
+     <img src="http://www.debian.org/logos/openlogo-nd-50.png" border="0" hspace="0" vspace="0" alt=""></a>
+        <a href="http://www.debian.org/">
+     <img src="http://www.debian.org/Pics/debian.png" border="0" hspace="0" vspace="0" alt="Debian Project"></a>
+        </div>
+        <br />
+        <table class="reddy" width="100%">
+        <tr>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-upperleft.png" align="left" border="0" hspace="0" vspace="0"
+     alt="" width="15" height="16"></td>
+        <td rowspan="2" class="reddy">Deferred uploads to Debian</td>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-upperright.png" align="right" border="0" hspace="0" vspace="0"
+     alt="" width="16" height="16"></td>
+        </tr>
+        <tr>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-lowerleft.png" align="left" border="0" hspace="0" vspace="0"
+     alt="" width="16" height="16"></td>
+        <td class="reddy">
+    <img src="http://www.debian.org/Pics/red-lowerright.png" align="right" border="0" hspace="0" vspace="0"
+     alt="" width="15" height="16"></td>
+        </tr>
+        </table>
+        """
+
+def footer():
+    res = "<p class=\"validate\">Timestamp: %s (UTC)</p>" % (time.strftime("%d.%m.%Y / %H:%M:%S", time.gmtime()))
+    res += """<a href="http://validator.w3.org/check?uri=referer">
+    <img border="0" src="http://www.w3.org/Icons/valid-html401" alt="Valid HTML 4.01!" height="31" width="88"></a>
+        <a href="http://jigsaw.w3.org/css-validator/check/referer">
+    <img border="0" src="http://jigsaw.w3.org/css-validator/images/vcss" alt="Valid CSS!"
+     height="31" width="88"></a>
+    """
+    res += "</body></html>"
+    return res
+
+def table_header():
+    return """<h1>Deferred uploads</h1>
+      <center><table border="0">
+        <tr>
+          <th align="center">Change</th>
+          <th align="center">Time remaining</th>
+          <th align="center">Uploader</th>
+          <th align="center">Closes</th>
+        </tr>
+        """
+    return res
+
+def table_footer():
+    return '</table><br/><p>non-NEW uploads are <a href="/deferred/">available</a>, see the <a href="ftp://ftp-master.debian.org/pub/UploadQueue/README">UploadQueue-README</a> for more information.</p></center><br/>\n'
+
+def table_row(changesname, delay, changed_by, closes):
+    global row_number
+
+    res = '<tr class="%s">'%((row_number%2) and 'odd' or 'even')
+    res += (3*'<td valign="top">%s</td>')%tuple(map(html_escape,(changesname,delay,changed_by)))
+    res += ('<td valign="top">%s</td>' %
+             ''.join(map(lambda close:  '<a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s">#%s</a><br>' % (close, close),closes)))
+    res += '</tr>\n'
+    row_number+=1
+    return res
+
+def get_upload_data(changesfn):
+    achanges = deb822.Changes(file(changesfn))
+    changesname = os.path.basename(changesfn)
+    delay = os.path.basename(os.path.dirname(changesfn))
+    m = re.match(r'([0-9]+)-day', delay)
+    if m:
+        delaydays = int(m.group(1))
+        remainingtime = (delaydays>0)*max(0,24*60*60+os.stat(changesfn).st_mtime-time.time())
+        delay = "%d days %02d:%02d" %(max(delaydays-1,0), int(remainingtime/3600),int(remainingtime/60)%60)
+    else:
+        remainingtime = 0
+
+    uploader = achanges.get('changed-by')
+    uploader = re.sub(r'^\s*(\S.*)\s+<.*>',r'\1',uploader)
+    if Cnf.has_key("Show-Deferred::LinkPath"):
+        isnew = 0
+        suites = database.get_suites(achanges['source'],src=1)
+        if 'unstable' not in suites and 'experimental' not in suites:
+            isnew = 1
+        for b in achanges['binary'].split():
+            suites = database.get_suites(b)
+            if 'unstable' not in suites and 'experimental' not in suites:
+                isnew = 1
+        if not isnew:
+            # we don't link .changes because we don't want other people to
+            # upload it with the existing signature.
+            for afn in map(lambda x: x['name'],achanges['files']):
+                lfn = os.path.join(Cnf["Show-Deferred::LinkPath"],afn)
+                qfn = os.path.join(os.path.dirname(changesfn),afn)
+                if os.path.islink(lfn):
+                    os.unlink(lfn)
+                if os.path.exists(qfn):
+                    os.symlink(qfn,lfn)
+                    os.chmod(qfn, 0644)
+    return (max(delaydays-1,0)*24*60*60+remainingtime, changesname, delay, uploader, achanges.get('closes','').split(),achanges)
+
+def list_uploads(filelist):
+    uploads = map(get_upload_data, filelist)
+    uploads.sort()
+    # print the summary page
+    print header()
+    if uploads:
+        print table_header()
+        print ''.join(map(lambda x: table_row(*x[1:5]), uploads))
+        print table_footer()
+    else:
+        print '<h1>Currently no deferred uploads to Debian</h1>'
+    print footer()
+    # machine readable summary
+    if Cnf.has_key("Show-Deferred::LinkPath"):
+        fn = os.path.join(Cnf["Show-Deferred::LinkPath"],'.status.tmp')
+        f = open(fn,"w")
+        try:
+            for u in uploads:
+                print >> f, "Changes: %s"%u[1]
+                fields = """Location: DEFERRED
+Delayed-Until: %s
+Delay-Remaining: %s"""%(time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(time.time()+u[0])),u[2])
+                print >> f, fields
+                print >> f, str(u[5]).rstrip()
+                open(os.path.join(Cnf["Show-Deferred::LinkPath"],u[1]),"w").write(str(u[5])+fields+'\n')
+                print >> f
+            f.close()
+            os.rename(os.path.join(Cnf["Show-Deferred::LinkPath"],'.status.tmp'),
+                      os.path.join(Cnf["Show-Deferred::LinkPath"],'status'))
+        except:
+            os.unlink(fn)
+            raise
+
+def usage (exit_code=0):
+    if exit_code:
+        f = sys.stderr
+    else:
+        f = sys.stdout
+    print >> f, """Usage: dak show-deferred
+  -h, --help                    show this help and exit.
+  -p, --link-path [path]        override output directory.
+  -d, --deferred-queue [path]   path to the deferred queue
+  """
+    sys.exit(exit_code)
+
+def init():
+    global Cnf, Options, Upload, projectB
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Show-Deferred::Options::Help"),
+                 ("p","link-path","Show-Deferred::LinkPath","HasArg"),
+                 ("d","deferred-queue","Show-Deferred::DeferredQueue","HasArg")]
+    args = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    for i in ["help"]:
+        if not Cnf.has_key("Show-Deferred::Options::%s" % (i)):
+            Cnf["Show-Deferred::Options::%s" % (i)] = ""
+    for i,j in [("DeferredQueue","--deferred-queue")]:
+        if not Cnf.has_key("Show-Deferred::%s" % (i)):
+            print >> sys.stderr, """Show-Deferred::%s is mandatory.
+  set via config file or command-line option %s"""%(i,j)
+
+    Options = Cnf.SubTree("Show-Deferred::Options")
+    if Options["help"]:
+        usage()
+    Upload = queue.Upload(Cnf)
+    projectB = Upload.projectB
+    return args
+
+def main():
+    args = init()
+    if len(args)!=0:
+        usage(1)
+
+    filelist = []
+    for r,d,f  in os.walk(Cnf["Show-Deferred::DeferredQueue"]):
+        filelist += map (lambda x: os.path.join(r,x),
+                         filter(lambda x: x.endswith('.changes'), f))
+    list_uploads(filelist)
+
+    available_changes = set(map(os.path.basename,filelist))
+    if Cnf.has_key("Show-Deferred::LinkPath"):
+        # remove dead links
+        for r,d,f in os.walk(Cnf["Show-Deferred::LinkPath"]):
+            for af in f:
+                afp = os.path.join(r,af)
+                if (not os.path.exists(afp) or
+                    (af.endswith('.changes') and af not in available_changes)):
+                    os.unlink(afp)
diff --git a/dak/show_new.py b/dak/show_new.py
new file mode 100755 (executable)
index 0000000..4b485f2
--- /dev/null
@@ -0,0 +1,242 @@
+#!/usr/bin/env python
+
+# Output html for packages in NEW
+# Copyright (C) 2007 Joerg Jaspert <joerg@debian.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <elmo> I'm James Troup, long term source of all evil in Debian. you may
+#        know me from such debian-devel-announce gems as "Serious
+#        Problems With ...."
+
+################################################################################
+
+import copy, os, sys, time
+import apt_pkg
+import examine_package
+from daklib import database
+from daklib import queue
+from daklib import utils
+
+# Globals
+Cnf = None
+Options = None
+Upload = None
+projectB = None
+sources = set()
+
+
+################################################################################
+################################################################################
+################################################################################
+
+def html_header(name, filestoexamine):
+    if name.endswith('.changes'):
+        name = ' '.join(name.split('_')[:2])
+    print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
+  <head>
+    <meta http-equiv="content-type" content="text/xhtml+xml; charset=utf-8"
+    />
+    <title>%(name)s - Debian NEW package overview</title>
+    <link type="text/css" rel="stylesheet" href="/style.css" />
+    <link rel="shortcut icon" href="http://www.debian.org/favicon.ico" />
+    <script type="text/javascript">
+      //<![CDATA[
+      <!--
+      function toggle(id, initial, display) {
+        var o = document.getElementById(id);
+        toggleObj(o, initial, display);
+      }
+      function show(id, display) {
+        var o = document.getElementById(id);
+        o.style.display = 'table-row-group';
+      }
+      function toggleObj(o, initial, display) {
+        if(! o.style.display)
+          o.style.display = initial;
+        if(o.style.display == display) {
+          o.style.display = "none";
+        } else {
+          o.style.display = display;
+        }
+      }
+      //-->
+      //]]>
+    </script>
+  </head>
+  <body id="NEW-details-page">
+    <div id="logo">
+      <a href="http://www.debian.org/">
+        <img src="http://www.debian.org/logos/openlogo-nd-50.png"
+        alt="debian logo" /></a>
+      <a href="http://www.debian.org/">
+        <img src="http://www.debian.org/Pics/debian.png"
+        alt="Debian Project" /></a>
+    </div>
+    <div id="titleblock">
+      <img src="http://www.debian.org/Pics/red-upperleft.png"
+      id="red-upperleft" alt="corner image"/>
+      <img src="http://www.debian.org/Pics/red-lowerleft.png"
+      id="red-lowerleft" alt="corner image"/>
+      <img src="http://www.debian.org/Pics/red-upperright.png"
+      id="red-upperright" alt="corner image"/>
+      <img src="http://www.debian.org/Pics/red-lowerright.png"
+      id="red-lowerright" alt="corner image"/>
+      <span class="title">
+        Debian NEW package overview for %(name)s
+      </span>
+    </div>
+    """%{"name":name}
+
+    # we assume only one source (.dsc) per changes here
+    print """
+    <div id="menu">
+      <p class="title">Navigation</p>
+      <p><a href="#changes" onclick="show('changes-body')">.changes</a></p>
+      <p><a href="#dsc" onclick="show('dsc-body')">.dsc</a></p>
+      <p><a href="#source-lintian" onclick="show('source-lintian-body')">source lintian</a></p>
+      """
+    for fn in filter(lambda x: x.endswith('.deb') or x.endswith('.udeb'),filestoexamine):
+        packagename = fn.split('_')[0]
+        print """
+        <p class="subtitle">%(pkg)s</p>
+        <p><a href="#binary-%(pkg)s-control" onclick="show('binary-%(pkg)s-control-body')">control file</a></p>
+        <p><a href="#binary-%(pkg)s-lintian" onclick="show('binary-%(pkg)s-lintian-body')">binary lintian</a></p>
+        <p><a href="#binary-%(pkg)s-contents" onclick="show('binary-%(pkg)s-contents-body')">.deb contents</a></p>
+        <p><a href="#binary-%(pkg)s-copyright" onclick="show('binary-%(pkg)s-copyright-body')">copyright</a></p>
+        <p><a href="#binary-%(pkg)s-file-listing" onclick="show('binary-%(pkg)s-file-listing-body')">file listing</a></p>
+        """%{"pkg":packagename}
+    print "    </div>"
+
+def html_footer():
+    print """    <p class="validate">Timestamp: %s (UTC)</p>"""% (time.strftime("%d.%m.%Y / %H:%M:%S", time.gmtime()))
+    print """    <p><a href="http://validator.w3.org/check?uri=referer">
+      <img src="http://www.w3.org/Icons/valid-html401" alt="Valid HTML 4.01!"
+      style="border: none; height: 31px; width: 88px" /></a>
+    <a href="http://jigsaw.w3.org/css-validator/check/referer">
+      <img src="http://jigsaw.w3.org/css-validator/images/vcss"
+      alt="Valid CSS!" style="border: none; height: 31px; width: 88px" /></a>
+    </p>
+  </body>
+</html>
+"""
+
+################################################################################
+
+
+def do_pkg(changes_file):
+    Upload.pkg.changes_file = changes_file
+    Upload.init_vars()
+    Upload.update_vars()
+    files = Upload.pkg.files
+    changes = Upload.pkg.changes
+
+    changes["suite"] = copy.copy(changes["distribution"])
+
+    # Find out what's new
+    new = queue.determine_new(changes, files, projectB, 0)
+
+    stdout_fd = sys.stdout
+
+    htmlname = changes["source"] + "_" + changes["version"] + ".html"
+    sources.add(htmlname)
+    # do not generate html output if that source/version already has one.
+    if not os.path.exists(os.path.join(Cnf["Show-New::HTMLPath"],htmlname)):
+        sys.stdout = open(os.path.join(Cnf["Show-New::HTMLPath"],htmlname),"w")
+
+        filestoexamine = []
+        for pkg in new.keys():
+            for fn in new[pkg]["files"]:
+                if ( files[fn].has_key("new") and not
+                     files[fn]["type"] in [ "orig.tar.gz", "orig.tar.bz2", "tar.gz", "tar.bz2", "diff.gz", "diff.bz2"] ):
+                    filestoexamine.append(fn)
+
+        html_header(changes["source"], filestoexamine)
+
+        queue.check_valid(new)
+        examine_package.display_changes(Upload.pkg.changes_file)
+
+        for fn in filter(lambda fn: fn.endswith(".dsc"), filestoexamine):
+            examine_package.check_dsc(fn)
+        for fn in filter(lambda fn: fn.endswith(".deb") or fn.endswith(".udeb"), filestoexamine):
+            examine_package.check_deb(fn)
+
+        html_footer()
+        if sys.stdout != stdout_fd:
+            sys.stdout.close()
+            sys.stdout = stdout_fd
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak show-new [OPTION]... [CHANGES]...
+  -h, --help                show this help and exit.
+  -p, --html-path [path]    override output directory.
+  """
+    sys.exit(exit_code)
+
+################################################################################
+
+def init():
+    global Cnf, Options, Upload, projectB
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Show-New::Options::Help"),
+                 ("p","html-path","Show-New::HTMLPath","HasArg")]
+
+    for i in ["help"]:
+        if not Cnf.has_key("Show-New::Options::%s" % (i)):
+            Cnf["Show-New::Options::%s" % (i)] = ""
+
+    changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv)
+    Options = Cnf.SubTree("Show-New::Options")
+
+    if Options["help"]:
+        usage()
+
+    Upload = queue.Upload(Cnf)
+
+    projectB = Upload.projectB
+
+    return changes_files
+
+
+################################################################################
+################################################################################
+
+def main():
+    changes_files = init()
+
+    examine_package.use_html=1
+
+    for changes_file in changes_files:
+        changes_file = utils.validate_changes_file_arg(changes_file, 0)
+        if not changes_file:
+            continue
+        print "\n" + changes_file
+        do_pkg (changes_file)
+    files = set(os.listdir(Cnf["Show-New::HTMLPath"]))
+    to_delete = filter(lambda x: x.endswith(".html"), files.difference(sources))
+    for f in to_delete:
+        os.remove(os.path.join(Cnf["Show-New::HTMLPath"],f))
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/split_done.py b/dak/split_done.py
new file mode 100755 (executable)
index 0000000..5f8fadd
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2004, 2005, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import glob, os, stat, time
+from daklib import utils
+
+################################################################################
+
+def main():
+    Cnf = utils.get_conf()
+    count = 0
+    move_date = int(time.time())-(30*84600)
+    os.chdir(Cnf["Dir::Queue::Done"])
+    files = glob.glob("%s/*" % (Cnf["Dir::Queue::Done"]))
+    for filename in files:
+        if os.path.isfile(filename):
+            filemtime = os.stat(filename)[stat.ST_MTIME]
+            if filemtime > move_date:
+                continue
+            mtime = time.gmtime(filemtime)
+            dirname = time.strftime("%Y/%m/%d", mtime)
+            if not os.path.exists(dirname):
+                print "Creating: %s" % (dirname)
+                os.makedirs(dirname)
+            dest = dirname + '/' + os.path.basename(filename)
+            if os.path.exists(dest):
+                utils.fubar("%s already exists." % (dest))
+            print "Move: %s -> %s" % (filename, dest)
+            os.rename(filename, dest)
+            count = count + 1
+    print "Moved %d files." % (count)
+
+############################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/stats.py b/dak/stats.py
new file mode 100755 (executable)
index 0000000..20a02b5
--- /dev/null
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+
+# Various statistical pr0nography fun and games
+# Copyright (C) 2000, 2001, 2002, 2003, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <aj>    can we change the standards instead?
+# <neuro> standards?
+# <aj>    whatever we're not conforming to
+# <aj>    if there's no written standard, why don't we declare linux as
+#         the defacto standard
+# <aj>    go us!
+
+# [aj's attempt to avoid ABI changes for released architecture(s)]
+
+################################################################################
+
+import pg, sys
+import apt_pkg
+from daklib import utils
+
+################################################################################
+
+Cnf = None
+projectB = None
+
+################################################################################
+
+def usage(exit_code=0):
+    print """Usage: dak stats MODE
+Print various stats.
+
+  -h, --help                show this help and exit.
+
+The following MODEs are available:
+
+  arch-space    - displays space used by each architecture
+  pkg-nums      - displays the number of packages by suite/architecture
+  daily-install - displays daily install stats suitable for graphing
+"""
+    sys.exit(exit_code)
+
+################################################################################
+
+def per_arch_space_use():
+    q = projectB.query("""
+SELECT a.arch_string as Architecture, sum(f.size)
+  FROM files f, binaries b, architecture a
+  WHERE a.id=b.architecture AND f.id=b.file
+  GROUP BY a.arch_string""")
+    print q
+    q = projectB.query("SELECT sum(size) FROM files WHERE filename ~ '.(diff.gz|tar.gz|dsc)$'")
+    print q
+
+################################################################################
+
+def daily_install_stats():
+    stats = {}
+    f = utils.open_file("2001-11")
+    for line in f.readlines():
+        split = line.strip().split('|')
+        program = split[1]
+        if program != "katie" and program != "process-accepted":
+            continue
+        action = split[2]
+        if action != "installing changes" and action != "installed":
+            continue
+        date = split[0][:8]
+        if not stats.has_key(date):
+            stats[date] = {}
+            stats[date]["packages"] = 0
+            stats[date]["size"] = 0.0
+        if action == "installing changes":
+            stats[date]["packages"] += 1
+        elif action == "installed":
+            stats[date]["size"] += float(split[5])
+
+    dates = stats.keys()
+    dates.sort()
+    for date in dates:
+        packages = stats[date]["packages"]
+        size = int(stats[date]["size"] / 1024.0 / 1024.0)
+        print "%s %s %s" % (date, packages, size)
+
+################################################################################
+
+def longest(list):
+    longest = 0
+    for i in list:
+        l = len(i)
+        if l > longest:
+            longest = l
+    return longest
+
+def suite_sort(a, b):
+    if Cnf.has_key("Suite::%s::Priority" % (a)):
+        a_priority = int(Cnf["Suite::%s::Priority" % (a)])
+    else:
+        a_priority = 0
+    if Cnf.has_key("Suite::%s::Priority" % (b)):
+        b_priority = int(Cnf["Suite::%s::Priority" % (b)])
+    else:
+        b_priority = 0
+    return cmp(a_priority, b_priority)
+
+def output_format(suite):
+    output_suite = []
+    for word in suite.split("-"):
+        output_suite.append(word[0])
+    return "-".join(output_suite)
+
+# Obvious query with GROUP BY and mapped names                  -> 50 seconds
+# GROUP BY but ids instead of suite/architecture names          -> 28 seconds
+# Simple query                                                  -> 14 seconds
+# Simple query into large dictionary + processing               -> 21 seconds
+# Simple query into large pre-created dictionary + processing   -> 18 seconds
+
+def number_of_packages():
+    arches = {}
+    arch_ids = {}
+    suites = {}
+    suite_ids = {}
+    d = {}
+    # Build up suite mapping
+    q = projectB.query("SELECT id, suite_name FROM suite")
+    suite_ql = q.getresult()
+    for i in suite_ql:
+        (sid, name) = i
+        suites[sid] = name
+        suite_ids[name] = sid
+    # Build up architecture mapping
+    q = projectB.query("SELECT id, arch_string FROM architecture")
+    for i in q.getresult():
+        (aid, name) = i
+        arches[aid] = name
+        arch_ids[name] = aid
+    # Pre-create the dictionary
+    for suite_id in suites.keys():
+        d[suite_id] = {}
+        for arch_id in arches.keys():
+            d[suite_id][arch_id] = 0
+    # Get the raw data for binaries
+    q = projectB.query("""
+SELECT ba.suite, b.architecture
+  FROM binaries b, bin_associations ba
+ WHERE b.id = ba.bin""")
+    # Simultate 'GROUP by suite, architecture' with a dictionary
+    for i in q.getresult():
+        (suite_id, arch_id) = i
+        d[suite_id][arch_id] = d[suite_id][arch_id] + 1
+    # Get the raw data for source
+    arch_id = arch_ids["source"]
+    q = projectB.query("""
+SELECT suite, count(suite) FROM src_associations GROUP BY suite;""")
+    for i in q.getresult():
+        (suite_id, count) = i
+        d[suite_id][arch_id] = d[suite_id][arch_id] + count
+    ## Print the results
+    # Setup
+    suite_list = suites.values()
+    suite_list.sort(suite_sort)
+    suite_id_list = []
+    suite_arches = {}
+    for suite in suite_list:
+        suite_id = suite_ids[suite]
+        suite_arches[suite_id] = {}
+        for arch in Cnf.ValueList("Suite::%s::Architectures" % (suite)):
+            suite_arches[suite_id][arch] = ""
+        suite_id_list.append(suite_id)
+    output_list = [ output_format(i) for i in suite_list ]
+    longest_suite = longest(output_list)
+    arch_list = arches.values()
+    arch_list.sort()
+    longest_arch = longest(arch_list)
+    # Header
+    output = (" "*longest_arch) + " |"
+    for suite in output_list:
+        output = output + suite.center(longest_suite)+" |"
+    output = output + "\n"+(len(output)*"-")+"\n"
+    # per-arch data
+    arch_list = arches.values()
+    arch_list.sort()
+    longest_arch = longest(arch_list)
+    for arch in arch_list:
+        arch_id = arch_ids[arch]
+        output = output + arch.center(longest_arch)+" |"
+        for suite_id in suite_id_list:
+            if suite_arches[suite_id].has_key(arch):
+                count = repr(d[suite_id][arch_id])
+            else:
+                count = "-"
+            output = output + count.rjust(longest_suite)+" |"
+        output = output + "\n"
+    print output
+
+################################################################################
+
+def main ():
+    global Cnf, projectB
+
+    Cnf = utils.get_conf()
+    Arguments = [('h',"help","Stats::Options::Help")]
+    for i in [ "help" ]:
+        if not Cnf.has_key("Stats::Options::%s" % (i)):
+            Cnf["Stats::Options::%s" % (i)] = ""
+
+    args = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Stats::Options")
+    if Options["Help"]:
+        usage()
+
+    if len(args) < 1:
+        utils.warn("dak stats requires a MODE argument")
+        usage(1)
+    elif len(args) > 1:
+        utils.warn("dak stats accepts only one MODE argument")
+        usage(1)
+    mode = args[0].lower()
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+
+    if mode == "arch-space":
+        per_arch_space_use()
+    elif mode == "pkg-nums":
+        number_of_packages()
+    elif mode == "daily-install":
+        daily_install_stats()
+    else:
+        utils.warn("unknown mode '%s'" % (mode))
+        usage(1)
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/test/001/1.dsc b/dak/test/001/1.dsc
new file mode 100644 (file)
index 0000000..dfdd92f
--- /dev/null
@@ -0,0 +1,22 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.0
+Source: amaya
+Version: 3.2.1-1
+Binary: amaya
+Maintainer: Steve Dunham <dunham@debian.org>
+Architecture: any
+Standards-Version: 2.4.0.0
+Files: 
+ 07f95f92b7cb0f12f7cf65ee5c5fbde2 4532418 amaya_3.2.1.orig.tar.gz
+ da06b390946745d9efaf9e7df8e05092 4817 amaya_3.2.1-1.diff.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.2 (GNU/Linux)
+Comment: For info see http://www.gnupg.org
+
+iD8DBQE5j091iPgEjVqvb1kRAvFtAJ0asUAaac6ebfR3YeaH16HjL7F3GwCfV+AQ
+rhYnRmVuNMa8oYSvL4hl/Yw=
+=EFAA
+-----END PGP SIGNATURE-----
diff --git a/dak/test/001/2.dsc b/dak/test/001/2.dsc
new file mode 100644 (file)
index 0000000..a6c9d85
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.0
+Source: amaya
+Version: 3.2.1-1
+Binary: amaya
+Maintainer: Steve Dunham <dunham@debian.org>
+Architecture: any
+Standards-Version: 2.4.0.0
+Files: 
+ 07f95f92b7cb0f12f7cf65ee5c5fbde2 4532418 amaya_3.2.1.orig.tar.gz
+ da06b390946745d9efaf9e7df8e05092 4817 amaya_3.2.1-1.diff.gz
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.2 (GNU/Linux)
+Comment: For info see http://www.gnupg.org
+
+iD8DBQE5j091iPgEjVqvb1kRAvFtAJ0asUAaac6ebfR3YeaH16HjL7F3GwCfV+AQ
+rhYnRmVuNMa8oYSvL4hl/Yw=
+=EFAA
+-----END PGP SIGNATURE-----
diff --git a/dak/test/001/3.dsc b/dak/test/001/3.dsc
new file mode 100644 (file)
index 0000000..211340e
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+Format: 1.0
+Source: amaya
+Version: 3.2.1-1
+Binary: amaya
+Maintainer: Steve Dunham <dunham@debian.org>
+Architecture: any
+Standards-Version: 2.4.0.0
+Files: 
+ 07f95f92b7cb0f12f7cf65ee5c5fbde2 4532418 amaya_3.2.1.orig.tar.gz
+ da06b390946745d9efaf9e7df8e05092 4817 amaya_3.2.1-1.diff.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.2 (GNU/Linux)
+Comment: For info see http://www.gnupg.org
+
+iD8DBQE5j091iPgEjVqvb1kRAvFtAJ0asUAaac6ebfR3YeaH16HjL7F3GwCfV+AQ
+rhYnRmVuNMa8oYSvL4hl/Yw=
+=EFAA
+-----END PGP SIGNATURE-----
diff --git a/dak/test/001/4.dsc b/dak/test/001/4.dsc
new file mode 100644 (file)
index 0000000..91e361f
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+Format: 1.0
+Source: amaya
+Version: 3.2.1-1
+Binary: amaya
+Maintainer: Steve Dunham <dunham@debian.org>
+Architecture: any
+Standards-Version: 2.4.0.0
+Files: 
+ 07f95f92b7cb0f12f7cf65ee5c5fbde2 4532418 amaya_3.2.1.orig.tar.gz
+ da06b390946745d9efaf9e7df8e05092 4817 amaya_3.2.1-1.diff.gz
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.2 (GNU/Linux)
+Comment: For info see http://www.gnupg.org
+iD8DBQE5j091iPgEjVqvb1kRAvFtAJ0asUAaac6ebfR3YeaH16HjL7F3GwCfV+AQ
+rhYnRmVuNMa8oYSvL4hl/Yw=
+=EFAA
+-----END PGP SIGNATURE-----
diff --git a/dak/test/001/5.dsc b/dak/test/001/5.dsc
new file mode 100644 (file)
index 0000000..db9d8d3
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.0
+Source: amaya
+Version: 3.2.1-1
+Binary: amaya
+Maintainer: Steve Dunham <dunham@debian.org>
+Architecture: any
+Standards-Version: 2.4.0.0
+Files: 
+ 07f95f92b7cb0f12f7cf65ee5c5fbde2 4532418 amaya_3.2.1.orig.tar.gz
+ da06b390946745d9efaf9e7df8e05092 4817 amaya_3.2.1-1.diff.gz
+
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.2 (GNU/Linux)
+Comment: For info see http://www.gnupg.org
+
+iD8DBQE5j091iPgEjVqvb1kRAvFtAJ0asUAaac6ebfR3YeaH16HjL7F3GwCfV+AQ
+rhYnRmVuNMa8oYSvL4hl/Yw=
+=EFAA
+-----END PGP SIGNATURE-----
diff --git a/dak/test/001/6.dsc b/dak/test/001/6.dsc
new file mode 100644 (file)
index 0000000..ae36d64
--- /dev/null
@@ -0,0 +1,23 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+
+Format: 1.0
+Source: amaya
+Version: 3.2.1-1
+Binary: amaya
+Maintainer: Steve Dunham <dunham@debian.org>
+Architecture: any
+Standards-Version: 2.4.0.0
+Files: 
+ 07f95f92b7cb0f12f7cf65ee5c5fbde2 4532418 amaya_3.2.1.orig.tar.gz
+ da06b390946745d9efaf9e7df8e05092 4817 amaya_3.2.1-1.diff.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.2 (GNU/Linux)
+Comment: For info see http://www.gnupg.org
+
+iD8DBQE5j091iPgEjVqvb1kRAvFtAJ0asUAaac6ebfR3YeaH16HjL7F3GwCfV+AQ
+rhYnRmVuNMa8oYSvL4hl/Yw=
+=EFAA
+-----END PGP SIGNATURE-----
diff --git a/dak/test/001/test.py b/dak/test/001/test.py
new file mode 100644 (file)
index 0000000..8238c20
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+
+# Check utils.parse_changes()'s .dsc file validation
+# Copyright (C) 2000, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, sys
+
+sys.path.append(os.path.abspath('../../'))
+
+import utils
+
+################################################################################
+
+def fail(message):
+    sys.stderr.write("%s\n" % (message))
+    sys.exit(1)
+
+################################################################################
+
+def main ():
+    # Valid .dsc
+    utils.parse_changes('1.dsc',1)
+
+    # Missing blank line before signature body
+    try:
+        utils.parse_changes('2.dsc',1)
+    except utils.invalid_dsc_format_exc, line:
+        if line != 14:
+            fail("Incorrect line number ('%s') for test #2." % (line))
+    else:
+        fail("Test #2 wasn't recognised as invalid.")
+
+    # Missing blank line after signature header
+    try:
+        utils.parse_changes('3.dsc',1)
+    except utils.invalid_dsc_format_exc, line:
+        if line != 14:
+            fail("Incorrect line number ('%s') for test #3." % (line))
+    else:
+        fail("Test #3 wasn't recognised as invalid.")
+
+    # No blank lines at all
+    try:
+        utils.parse_changes('4.dsc',1)
+    except utils.invalid_dsc_format_exc, line:
+        if line != 19:
+            fail("Incorrect line number ('%s') for test #4." % (line))
+    else:
+        fail("Test #4 wasn't recognised as invalid.")
+
+    # Extra blank line before signature body
+    try:
+        utils.parse_changes('5.dsc',1)
+    except utils.invalid_dsc_format_exc, line:
+        if line != 15:
+            fail("Incorrect line number ('%s') for test #5." % (line))
+    else:
+        fail("Test #5 wasn't recognised as invalid.")
+
+    # Extra blank line after signature header
+    try:
+        utils.parse_changes('6.dsc',1)
+    except utils.invalid_dsc_format_exc, line:
+        if line != 5:
+            fail("Incorrect line number ('%s') for test #6." % (line))
+    else:
+        fail("Test #6 wasn't recognised as invalid.")
+
+    # Valid .dsc ; ignoring errors
+    utils.parse_changes('1.dsc', 0)
+
+    # Invalid .dsc ; ignoring errors
+    utils.parse_changes('2.dsc', 0)
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/test/002/empty.changes b/dak/test/002/empty.changes
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/dak/test/002/test.py b/dak/test/002/test.py
new file mode 100644 (file)
index 0000000..919a70a
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+# Check utils.parse_changes()'s for handling empty files
+# Copyright (C) 2000, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, sys
+
+sys.path.append(os.path.abspath('../../'))
+
+import utils
+
+################################################################################
+
+def fail(message):
+    sys.stderr.write("%s\n" % (message))
+    sys.exit(1)
+
+################################################################################
+
+def main ():
+    # Empty .changes file; should raise a 'parse error' exception.
+    try:
+        utils.parse_changes('empty.changes', 0)
+    except utils.changes_parse_error_exc, line:
+        if line != "[Empty changes file]":
+            fail("Returned exception with unexcpected error message `%s'." % (line))
+    else:
+        fail("Didn't raise a 'parse error' exception for a zero-length .changes file.")
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/test/003/krb5_1.2.2-4_m68k.changes b/dak/test/003/krb5_1.2.2-4_m68k.changes
new file mode 100644 (file)
index 0000000..9d264c1
--- /dev/null
@@ -0,0 +1,54 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Format: 1.7
+Date: Fri, 20 Apr 2001 02:47:21 -0400
+Source: krb5
+Binary: krb5-kdc krb5-doc krb5-rsh-server libkrb5-dev libkrb53 krb5-ftpd
+ krb5-clients krb5-user libkadm54 krb5-telnetd krb5-admin-server
+Architecture: m68k
+Version: 1.2.2-4
+Distribution: unstable
+Urgency: low
+Maintainer: buildd m68k user account <buildd@ax.westfalen.de>
+Changed-By: Sam Hartman <hartmans@debian.org>
+Description: 
+ krb5-admin-server - Mit Kerberos master server (kadmind)
+ krb5-clients - Secure replacements for ftp, telnet and rsh using MIT Kerberos
+ krb5-ftpd  - Secure FTP server supporting MIT Kerberos
+ krb5-kdc   - Mit Kerberos key server (KDC)
+ krb5-rsh-server - Secure replacements for rshd and rlogind  using MIT Kerberos
+ krb5-telnetd - Secure telnet server supporting MIT Kerberos
+ krb5-user  - Basic programs to authenticate using MIT Kerberos
+ libkadm54  - MIT Kerberos administration runtime libraries
+ libkrb5-dev - Headers and development libraries for MIT Kerberos
+ libkrb53   - MIT Kerberos runtime libraries
+Closes: 94407
+Changes: 
+ krb5 (1.2.2-4) unstable; urgency=low
+ .
+   * Fix shared libraries to build with gcc not ld to properly include
+     -lgcc symbols, closes: #94407
+Files: 
+ 563dac1cdd3ba922f9301fe074fbfc80 65836 non-us/main optional libkadm54_1.2.2-4_m68k.deb
+ bb620f589c17ab0ebea1aa6e10ca52ad 272198 non-us/main optional libkrb53_1.2.2-4_m68k.deb
+ 40af6e64b3030a179e0de25bd95c95e9 143264 non-us/main optional krb5-user_1.2.2-4_m68k.deb
+ ffe4e5e7b2cab162dc608d56278276cf 141870 non-us/main optional krb5-clients_1.2.2-4_m68k.deb
+ 4fe01d1acb4b82ce0b8b72652a9a15ae 54592 non-us/main optional krb5-rsh-server_1.2.2-4_m68k.deb
+ b3c8c617ea72008a33b869b75d2485bf 41292 non-us/main optional krb5-ftpd_1.2.2-4_m68k.deb
+ 5908f8f60fe536d7bfc1ef3fdd9d74cc 42090 non-us/main optional krb5-telnetd_1.2.2-4_m68k.deb
+ 650ea769009a312396e56503d0059ebc 160236 non-us/main optional krb5-kdc_1.2.2-4_m68k.deb
+ 399c9de4e9d7d0b0f5626793808a4391 160392 non-us/main optional krb5-admin-server_1.2.2-4_m68k.deb
+ 6f962fe530c3187e986268b4e4d27de9 398662 non-us/main optional libkrb5-dev_1.2.2-4_m68k.deb
+
+-----BEGIN PGP SIGNATURE-----
+Version: 2.6.3i
+Charset: noconv
+
+iQCVAwUBOvVPPm547I3m3eHJAQHyaQP+M7RXVEqZ2/xHiPzaPcZRJ4q7o0zbMaU8
+qG/Mi6kuR1EhRNMjMH4Cp6ctbhRDHK5FR/8v7UkOd+ETDAhiw7eqJnLC60EZxZ/H
+CiOs8JklAXDERkQ3i7EYybv46Gxx91pIs2nE4xVKnG16d/wFELWMBLY6skF1B2/g
+zZju3cuFCCE=
+=Vm59
+-----END PGP SIGNATURE-----
+
+
diff --git a/dak/test/003/test.py b/dak/test/003/test.py
new file mode 100755 (executable)
index 0000000..ce07c11
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+# Check utils.parse_changes()'s for handling of multi-line fields
+# Copyright (C) 2000, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# The deal here is that for the first 6 months of dak's
+# implementation it has been misparsing multi-line fields in .changes
+# files; specifically multi-line fields where there _is_ data on the
+# first line. So, for example:
+
+# Foo: bar baz
+#  bat bant
+
+# Became "foo: bar bazbat bant" rather than "foo: bar baz\nbat bant"
+
+################################################################################
+
+import os, sys
+
+sys.path.append(os.path.abspath('../../'))
+
+import utils
+
+################################################################################
+
+def fail(message):
+    sys.stderr.write("%s\n" % (message))
+    sys.exit(1)
+
+################################################################################
+
+def main ():
+    # Valid .changes file with a multi-line Binary: field
+    try:
+        changes = utils.parse_changes('krb5_1.2.2-4_m68k.changes', 0)
+    except utils.changes_parse_error_exc, line:
+        fail("parse_changes() returned an exception with error message `%s'." % (line))
+
+    o = changes.get("binary", "")
+    if o != "":
+        del changes["binary"]
+    changes["binary"] = {}
+    for j in o.split():
+        changes["binary"][j] = 1
+
+    if not changes["binary"].has_key("krb5-ftpd"):
+        fail("parse_changes() is broken; 'krb5-ftpd' is not in the Binary: dictionary.")
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/test/004/test.py b/dak/test/004/test.py
new file mode 100755 (executable)
index 0000000..4aa6b48
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+
+# Check utils.extract_component_from_section()
+# Copyright (C) 2000, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, sys
+
+sys.path.append(os.path.abspath('../../'))
+
+import utils
+
+################################################################################
+
+def fail(message):
+    sys.stderr.write("%s\n" % (message))
+    sys.exit(1)
+
+################################################################################
+
+# prefix: non-US
+# component: main, contrib, non-free
+# section: games, admin, libs, [...]
+
+# [1] Order is as above.
+# [2] Prefix is optional for the default archive, but mandatory when
+#     uploads are going anywhere else.
+# [3] Default component is main and may be omitted.
+# [4] Section is optional.
+# [5] Prefix is case insensitive
+# [6] Everything else is case sensitive.
+
+def test(input, output):
+    result = utils.extract_component_from_section(input)
+    if result != output:
+        fail ("%s -> %r [should have been %r]" % (input, result, output))
+
+def main ():
+    # Err, whoops?  should probably be "utils", "main"...
+    input = "main/utils"; output = ("main/utils", "main")
+    test (input, output)
+
+
+    # Validate #3
+    input = "utils"; output = ("utils", "main")
+    test (input, output)
+
+    input = "non-free/libs"; output = ("non-free/libs", "non-free")
+    test (input, output)
+
+    input = "contrib/net"; output = ("contrib/net", "contrib")
+    test (input, output)
+
+
+    # Validate #3 with a prefix
+    input = "non-US"; output = ("non-US", "non-US/main")
+    test (input, output)
+
+
+    # Validate #4
+    input = "main"; output = ("main", "main")
+    test (input, output)
+
+    input = "contrib"; output = ("contrib", "contrib")
+    test (input, output)
+
+    input = "non-free"; output = ("non-free", "non-free")
+    test (input, output)
+
+
+    # Validate #4 with a prefix
+    input = "non-US/main"; output = ("non-US/main", "non-US/main")
+    test (input, output)
+
+    input = "non-US/contrib"; output = ("non-US/contrib", "non-US/contrib")
+    test (input, output)
+
+    input = "non-US/non-free"; output = ("non-US/non-free", "non-US/non-free")
+    test (input, output)
+
+
+    # Validate #5
+    input = "non-us"; output = ("non-us", "non-US/main")
+    test (input, output)
+
+    input = "non-us/contrib"; output = ("non-us/contrib", "non-US/contrib")
+    test (input, output)
+
+
+    # Validate #6 (section)
+    input = "utIls"; output = ("utIls", "main")
+    test (input, output)
+
+    # Others..
+    input = "non-US/libs"; output = ("non-US/libs", "non-US/main")
+    test (input, output)
+    input = "non-US/main/libs"; output = ("non-US/main/libs", "non-US/main")
+    test (input, output)
+    input = "non-US/contrib/libs"; output = ("non-US/contrib/libs", "non-US/contrib")
+    test (input, output)
+    input = "non-US/non-free/libs"; output = ("non-US/non-free/libs", "non-US/non-free")
+    test (input, output)
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/test/005/bogus-post.changes b/dak/test/005/bogus-post.changes
new file mode 100644 (file)
index 0000000..95e5a1f
--- /dev/null
@@ -0,0 +1,41 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.7
+Date: Tue,  9 Sep 2003 01:16:01 +0100
+Source: gawk
+Binary: gawk
+Architecture: source i386
+Version: 1:3.1.3-2
+Distribution: unstable
+Urgency: low
+Maintainer: James Troup <james@nocrew.org>
+Changed-By: James Troup <james@nocrew.org>
+Description: 
+ gawk       - GNU awk, a pattern scanning and processing language
+Closes: 204699 204701
+Changes: 
+ gawk (1:3.1.3-2) unstable; urgency=low
+ .
+   * debian/control (Standards-Version): bump to 3.6.1.0.
+ .
+   * 02_fix-ascii.dpatch: new patch from upstream to fix [[:ascii:]].
+     Thanks to <vle@gmx.net> for reporting the bug and forwarding it
+     upstream.  Closes: #204701
+ .
+   * 03_fix-high-char-ranges.dpatch: new patch from upstream to fix
+     [\x80-\xff].  Thanks to <vle@gmx.net> for reporting the bug and
+     forwarding it upstream.  Closes: #204699
+Files: 
+ 0e6542c48bcc9d9586fc8ebe4e7242a4 561 interpreters optional gawk_3.1.3-2.dsc
+ 50a29dce4a2c6e2ac38069eb7c41d9c4 8302 interpreters optional gawk_3.1.3-2.diff.gz
+ 5a255c7b421ac699804212e10205f22d 871114 interpreters optional gawk_3.1.3-2_i386.deb
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.6 (GNU/Linux)
+
+iEYEARECAAYFAj9dHWsACgkQgD/uEicUG7DUnACglndvU4LCA0/k36Qp873N0Sau
+fCwAoMdgIOUBcUfMqXvVnxdW03ev5bNB
+=O7Gh
+-----END PGP SIGNATURE-----
+You: have been 0wned
diff --git a/dak/test/005/bogus-pre.changes b/dak/test/005/bogus-pre.changes
new file mode 100644 (file)
index 0000000..0234d8b
--- /dev/null
@@ -0,0 +1,41 @@
+You: have been 0wned
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.7
+Date: Tue,  9 Sep 2003 01:16:01 +0100
+Source: gawk
+Binary: gawk
+Architecture: source i386
+Version: 1:3.1.3-2
+Distribution: unstable
+Urgency: low
+Maintainer: James Troup <james@nocrew.org>
+Changed-By: James Troup <james@nocrew.org>
+Description: 
+ gawk       - GNU awk, a pattern scanning and processing language
+Closes: 204699 204701
+Changes: 
+ gawk (1:3.1.3-2) unstable; urgency=low
+ .
+   * debian/control (Standards-Version): bump to 3.6.1.0.
+ .
+   * 02_fix-ascii.dpatch: new patch from upstream to fix [[:ascii:]].
+     Thanks to <vle@gmx.net> for reporting the bug and forwarding it
+     upstream.  Closes: #204701
+ .
+   * 03_fix-high-char-ranges.dpatch: new patch from upstream to fix
+     [\x80-\xff].  Thanks to <vle@gmx.net> for reporting the bug and
+     forwarding it upstream.  Closes: #204699
+Files: 
+ 0e6542c48bcc9d9586fc8ebe4e7242a4 561 interpreters optional gawk_3.1.3-2.dsc
+ 50a29dce4a2c6e2ac38069eb7c41d9c4 8302 interpreters optional gawk_3.1.3-2.diff.gz
+ 5a255c7b421ac699804212e10205f22d 871114 interpreters optional gawk_3.1.3-2_i386.deb
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.6 (GNU/Linux)
+
+iEYEARECAAYFAj9dHWsACgkQgD/uEicUG7DUnACglndvU4LCA0/k36Qp873N0Sau
+fCwAoMdgIOUBcUfMqXvVnxdW03ev5bNB
+=O7Gh
+-----END PGP SIGNATURE-----
diff --git a/dak/test/005/test.py b/dak/test/005/test.py
new file mode 100755 (executable)
index 0000000..b5d3bbc
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+# Check utils.parse_changes() correctly ignores data outside the signed area
+# Copyright (C) 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, sys
+
+sys.path.append(os.path.abspath('../../'))
+
+import utils
+
+################################################################################
+
+def fail(message):
+    sys.stderr.write("%s\n" % (message))
+    sys.exit(1)
+
+################################################################################
+
+def main ():
+    for file in [ "valid", "bogus-pre", "bogus-post" ]:
+        for strict_whitespace in [ 0, 1 ]:
+            try:
+                changes = utils.parse_changes("%s.changes" % (file), strict_whitespace)
+            except utils.changes_parse_error_exc, line:
+                fail("%s[%s]: parse_changes() returned an exception with error message `%s'." % (file, strict_whitespace, line))
+            oh_dear = changes.get("you")
+            if oh_dear:
+                fail("%s[%s]: parsed and accepted unsigned data!" % (file, strict_whitespace))
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/test/005/valid.changes b/dak/test/005/valid.changes
new file mode 100644 (file)
index 0000000..0e77d27
--- /dev/null
@@ -0,0 +1,40 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.7
+Date: Tue,  9 Sep 2003 01:16:01 +0100
+Source: gawk
+Binary: gawk
+Architecture: source i386
+Version: 1:3.1.3-2
+Distribution: unstable
+Urgency: low
+Maintainer: James Troup <james@nocrew.org>
+Changed-By: James Troup <james@nocrew.org>
+Description: 
+ gawk       - GNU awk, a pattern scanning and processing language
+Closes: 204699 204701
+Changes: 
+ gawk (1:3.1.3-2) unstable; urgency=low
+ .
+   * debian/control (Standards-Version): bump to 3.6.1.0.
+ .
+   * 02_fix-ascii.dpatch: new patch from upstream to fix [[:ascii:]].
+     Thanks to <vle@gmx.net> for reporting the bug and forwarding it
+     upstream.  Closes: #204701
+ .
+   * 03_fix-high-char-ranges.dpatch: new patch from upstream to fix
+     [\x80-\xff].  Thanks to <vle@gmx.net> for reporting the bug and
+     forwarding it upstream.  Closes: #204699
+Files: 
+ 0e6542c48bcc9d9586fc8ebe4e7242a4 561 interpreters optional gawk_3.1.3-2.dsc
+ 50a29dce4a2c6e2ac38069eb7c41d9c4 8302 interpreters optional gawk_3.1.3-2.diff.gz
+ 5a255c7b421ac699804212e10205f22d 871114 interpreters optional gawk_3.1.3-2_i386.deb
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.0.6 (GNU/Linux)
+
+iEYEARECAAYFAj9dHWsACgkQgD/uEicUG7DUnACglndvU4LCA0/k36Qp873N0Sau
+fCwAoMdgIOUBcUfMqXvVnxdW03ev5bNB
+=O7Gh
+-----END PGP SIGNATURE-----
diff --git a/dak/test/006/test.py b/dak/test/006/test.py
new file mode 100755 (executable)
index 0000000..b7594bc
--- /dev/null
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Test utils.fix_maintainer()
+# Copyright (C) 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, sys
+
+sys.path.append(os.path.abspath('../../'))
+
+import utils
+
+################################################################################
+
+def fail(message):
+    sys.stderr.write("%s\n" % (message))
+    sys.exit(1)
+
+################################################################################
+
+def check_valid(s, xa, xb, xc, xd):
+    (a, b, c, d) = utils.fix_maintainer(s)
+    if a != xa:
+        fail("rfc822_maint: %s (returned) != %s (expected [From: '%s']" % (a, xa, s))
+    if b != xb:
+        fail("rfc2047_maint: %s (returned) != %s (expected [From: '%s']" % (b, xb, s))
+    if c != xc:
+        fail("name: %s (returned) != %s (expected [From: '%s']" % (c, xc, s))
+    if d != xd:
+        fail("email: %s (returned) != %s (expected [From: '%s']" % (d, xd, s))
+
+def check_invalid(s):
+    try:
+        utils.fix_maintainer(s)
+        fail("%s was parsed successfully but is expected to be invalid." % (s))
+    except utils.ParseMaintError, unused:
+        pass
+
+def main ():
+    # Check Valid UTF-8 maintainer field
+    s = "Noèl Köthe <noel@debian.org>"
+    xa = "Noèl Köthe <noel@debian.org>"
+    xb = "=?utf-8?b?Tm/DqGwgS8O2dGhl?= <noel@debian.org>"
+    xc = "Noèl Köthe"
+    xd = "noel@debian.org"
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check valid ISO-8859-1 maintainer field
+    s = "Noèl Köthe <noel@debian.org>"
+    xa = "Noèl Köthe <noel@debian.org>"
+    xb = "=?iso-8859-1?q?No=E8l_K=F6the?= <noel@debian.org>"
+    xc = "Noèl Köthe"
+    xd = "noel@debian.org"
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check valid ASCII maintainer field
+    s = "James Troup <james@nocrew.org>"
+    xa = "James Troup <james@nocrew.org>"
+    xb = "James Troup <james@nocrew.org>"
+    xc = "James Troup"
+    xd = "james@nocrew.org"
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check "Debian vs RFC822" fixup of names with '.' or ',' in them
+    s = "James J. Troup <james@nocrew.org>"
+    xa = "james@nocrew.org (James J. Troup)"
+    xb = "james@nocrew.org (James J. Troup)"
+    xc = "James J. Troup"
+    xd = "james@nocrew.org"
+    check_valid(s, xa, xb, xc, xd)
+    s = "James J, Troup <james@nocrew.org>"
+    xa = "james@nocrew.org (James J, Troup)"
+    xb = "james@nocrew.org (James J, Troup)"
+    xc = "James J, Troup"
+    xd = "james@nocrew.org"
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check just-email form
+    s = "james@nocrew.org"
+    xa = " <james@nocrew.org>"
+    xb = " <james@nocrew.org>"
+    xc = ""
+    xd = "james@nocrew.org"
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check bracketed just-email form
+    s = "<james@nocrew.org>"
+    xa = " <james@nocrew.org>"
+    xb = " <james@nocrew.org>"
+    xc = ""
+    xd = "james@nocrew.org"
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check Krazy quoted-string local part email address
+    s = "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>"
+    xa = "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>"
+    xb = "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>"
+    xc = "Cris van Pelt"
+    xd = "\"Cris van Pelt\"@tribe.eu.org"
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check empty string
+    s = xa = xb = xc = xd = ""
+    check_valid(s, xa, xb, xc, xd)
+
+    # Check for missing email address
+    check_invalid("James Troup")
+    # Check for invalid email address
+    check_invalid("James Troup <james@nocrew.org")
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/dak/transitions.py b/dak/transitions.py
new file mode 100755 (executable)
index 0000000..b7e5065
--- /dev/null
@@ -0,0 +1,490 @@
+#!/usr/bin/env python
+
+# Display, edit and check the release manager's transition file.
+# Copyright (C) 2008 Joerg Jaspert <joerg@debian.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+# <elmo> if klecker.d.o died, I swear to god, I'm going to migrate to gentoo.
+
+################################################################################
+
+import os, pg, sys, time, errno, fcntl, tempfile, pwd, re
+import apt_pkg
+from daklib import database
+from daklib import utils
+from daklib.dak_exceptions import TransitionsError
+import yaml
+
+# Globals
+Cnf = None
+Options = None
+projectB = None
+
+re_broken_package = re.compile(r"[a-zA-Z]\w+\s+\-.*")
+
+################################################################################
+
+#####################################
+#### This may run within sudo !! ####
+#####################################
+def init():
+    global Cnf, Options, projectB
+
+    apt_pkg.init()
+
+    Cnf = utils.get_conf()
+
+    Arguments = [('h',"help","Edit-Transitions::Options::Help"),
+                 ('e',"edit","Edit-Transitions::Options::Edit"),
+                 ('i',"import","Edit-Transitions::Options::Import", "HasArg"),
+                 ('c',"check","Edit-Transitions::Options::Check"),
+                 ('s',"sudo","Edit-Transitions::Options::Sudo"),
+                 ('n',"no-action","Edit-Transitions::Options::No-Action")]
+
+    for i in ["help", "no-action", "edit", "import", "check", "sudo"]:
+        if not Cnf.has_key("Edit-Transitions::Options::%s" % (i)):
+            Cnf["Edit-Transitions::Options::%s" % (i)] = ""
+
+    apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Edit-Transitions::Options")
+
+    if Options["help"]:
+        usage()
+
+    whoami = os.getuid()
+    whoamifull = pwd.getpwuid(whoami)
+    username = whoamifull[0]
+    if username != "dak":
+        print "Non-dak user: %s" % username
+        Options["sudo"] = "y"
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    database.init(Cnf, projectB)
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: transitions [OPTION]...
+Update and check the release managers transition file.
+
+Options:
+
+  -h, --help                show this help and exit.
+  -e, --edit                edit the transitions file
+  -i, --import <file>       check and import transitions from file
+  -c, --check               check the transitions file, remove outdated entries
+  -S, --sudo                use sudo to update transitions file
+  -n, --no-action           don't do anything (only affects check)"""
+
+    sys.exit(exit_code)
+
+################################################################################
+
+#####################################
+#### This may run within sudo !! ####
+#####################################
+def load_transitions(trans_file):
+    # Parse the yaml file
+    sourcefile = file(trans_file, 'r')
+    sourcecontent = sourcefile.read()
+    failure = False
+    try:
+        trans = yaml.load(sourcecontent)
+    except yaml.YAMLError, exc:
+        # Someone fucked it up
+        print "ERROR: %s" % (exc)
+        return None
+
+    # lets do further validation here
+    checkkeys = ["source", "reason", "packages", "new", "rm"]
+
+    # If we get an empty definition - we just have nothing to check, no transitions defined
+    if type(trans) != dict:
+        # This can be anything. We could have no transitions defined. Or someone totally fucked up the
+        # file, adding stuff in a way we dont know or want. Then we set it empty - and simply have no
+        # transitions anymore. User will see it in the information display after he quit the editor and
+        # could fix it
+        trans = ""
+        return trans
+
+    try:
+        for test in trans:
+            t = trans[test]
+
+            # First check if we know all the keys for the transition and if they have
+            # the right type (and for the packages also if the list has the right types
+            # included, ie. not a list in list, but only str in the list)
+            for key in t:
+                if key not in checkkeys:
+                    print "ERROR: Unknown key %s in transition %s" % (key, test)
+                    failure = True
+
+                if key == "packages":
+                    if type(t[key]) != list:
+                        print "ERROR: Unknown type %s for packages in transition %s." % (type(t[key]), test)
+                        failure = True
+                    try:
+                        for package in t["packages"]:
+                            if type(package) != str:
+                                print "ERROR: Packages list contains invalid type %s (as %s) in transition %s" % (type(package), package, test)
+                                failure = True
+                            if re_broken_package.match(package):
+                                # Someone had a space too much (or not enough), we have something looking like
+                                # "package1 - package2" now.
+                                print "ERROR: Invalid indentation of package list in transition %s, around package(s): %s" % (test, package)
+                                failure = True
+                    except TypeError:
+                        # In case someone has an empty packages list
+                        print "ERROR: No packages defined in transition %s" % (test)
+                        failure = True
+                        continue
+
+                elif type(t[key]) != str:
+                    if key == "new" and type(t[key]) == int:
+                        # Ok, debian native version
+                        continue
+                    else:
+                        print "ERROR: Unknown type %s for key %s in transition %s" % (type(t[key]), key, test)
+                        failure = True
+
+            # And now the other way round - are all our keys defined?
+            for key in checkkeys:
+                if key not in t:
+                    print "ERROR: Missing key %s in transition %s" % (key, test)
+                    failure = True
+    except TypeError:
+        # In case someone defined very broken things
+        print "ERROR: Unable to parse the file"
+        failure = True
+
+
+    if failure:
+        return None
+
+    return trans
+
+################################################################################
+
+#####################################
+#### This may run within sudo !! ####
+#####################################
+def lock_file(f):
+    for retry in range(10):
+        lock_fd = os.open(f, os.O_RDWR | os.O_CREAT)
+        try:
+            fcntl.lockf(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+            return lock_fd
+        except OSError, e:
+            if errno.errorcode[e.errno] == 'EACCES' or errno.errorcode[e.errno] == 'EEXIST':
+                print "Unable to get lock for %s (try %d of 10)" % \
+                        (file, retry+1)
+                time.sleep(60)
+            else:
+                raise
+
+    utils.fubar("Couldn't obtain lock for %s." % (f))
+
+################################################################################
+
+#####################################
+#### This may run within sudo !! ####
+#####################################
+def write_transitions(from_trans):
+    """Update the active transitions file safely.
+       This function takes a parsed input file (which avoids invalid
+       files or files that may be be modified while the function is
+       active), and ensure the transitions file is updated atomically
+       to avoid locks."""
+
+    trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
+    trans_temp = trans_file + ".tmp"
+
+    trans_lock = lock_file(trans_file)
+    temp_lock  = lock_file(trans_temp)
+
+    destfile = file(trans_temp, 'w')
+    yaml.dump(from_trans, destfile, default_flow_style=False)
+    destfile.close()
+
+    os.rename(trans_temp, trans_file)
+    os.close(temp_lock)
+    os.close(trans_lock)
+
+################################################################################
+
+##########################################
+#### This usually runs within sudo !! ####
+##########################################
+def write_transitions_from_file(from_file):
+    """We have a file we think is valid; if we're using sudo, we invoke it
+       here, otherwise we just parse the file and call write_transitions"""
+
+    # Lets check if from_file is in the directory we expect it to be in
+    if not os.path.abspath(from_file).startswith(Cnf["Transitions::TempPath"]):
+        print "Will not accept transitions file outside of %s" % (Cnf["Transitions::TempPath"])
+        sys.exit(3)
+
+    if Options["sudo"]:
+        os.spawnl(os.P_WAIT, "/usr/bin/sudo", "/usr/bin/sudo", "-u", "dak", "-H",
+              "/usr/local/bin/dak", "transitions", "--import", from_file)
+    else:
+        trans = load_transitions(from_file)
+        if trans is None:
+            raise TransitionsError, "Unparsable transitions file %s" % (file)
+        write_transitions(trans)
+
+################################################################################
+
+def temp_transitions_file(transitions):
+    # NB: file is unlinked by caller, but fd is never actually closed.
+    # We need the chmod, as the file is (most possibly) copied from a
+    # sudo-ed script and would be unreadable if it has default mkstemp mode
+
+    (fd, path) = tempfile.mkstemp("", "transitions", Cnf["Transitions::TempPath"])
+    os.chmod(path, 0644)
+    f = open(path, "w")
+    yaml.dump(transitions, f, default_flow_style=False)
+    return path
+
+################################################################################
+
+def edit_transitions():
+    trans_file = Cnf["Dinstall::Reject::ReleaseTransitions"]
+    edit_file = temp_transitions_file(load_transitions(trans_file))
+
+    editor = os.environ.get("EDITOR", "vi")
+
+    while True:
+        result = os.system("%s %s" % (editor, edit_file))
+        if result != 0:
+            os.unlink(edit_file)
+            utils.fubar("%s invocation failed for %s, not removing tempfile." % (editor, edit_file))
+
+        # Now try to load the new file
+        test = load_transitions(edit_file)
+
+        if test == None:
+            # Edit is broken
+            print "Edit was unparsable."
+            prompt = "[E]dit again, Drop changes?"
+            default = "E"
+        else:
+            print "Edit looks okay.\n"
+            print "The following transitions are defined:"
+            print "------------------------------------------------------------------------"
+            transition_info(test)
+
+            prompt = "[S]ave, Edit again, Drop changes?"
+            default = "S"
+
+        answer = "XXX"
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            if answer == "":
+                answer = default
+            answer = answer[:1].upper()
+
+        if answer == 'E':
+            continue
+        elif answer == 'D':
+            os.unlink(edit_file)
+            print "OK, discarding changes"
+            sys.exit(0)
+        elif answer == 'S':
+            # Ready to save
+            break
+        else:
+            print "You pressed something you shouldn't have :("
+            sys.exit(1)
+
+    # We seem to be done and also have a working file. Copy over.
+    write_transitions_from_file(edit_file)
+    os.unlink(edit_file)
+
+    print "Transitions file updated."
+
+################################################################################
+
+def check_transitions(transitions):
+    to_dump = 0
+    to_remove = []
+    # Now look through all defined transitions
+    for trans in transitions:
+        t = transitions[trans]
+        source = t["source"]
+        expected = t["new"]
+
+        # Will be None if nothing is in testing.
+        current = database.get_suite_version(source, "testing")
+
+        print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
+
+        if current == None:
+            # No package in testing
+            print "Transition source %s not in testing, transition still ongoing." % (source)
+        else:
+            compare = apt_pkg.VersionCompare(current, expected)
+            if compare < 0:
+                # This is still valid, the current version in database is older than
+                # the new version we wait for
+                print "This transition is still ongoing, we currently have version %s" % (current)
+            else:
+                print "REMOVE: This transition is over, the target package reached testing. REMOVE"
+                print "%s wanted version: %s, has %s" % (source, expected, current)
+                to_remove.append(trans)
+                to_dump = 1
+        print "-------------------------------------------------------------------------"
+
+    if to_dump:
+        prompt = "Removing: "
+        for remove in to_remove:
+            prompt += remove
+            prompt += ","
+
+        prompt += " Commit Changes? (y/N)"
+        answer = ""
+
+        if Options["no-action"]:
+            answer="n"
+        else:
+            answer = utils.our_raw_input(prompt).lower()
+
+        if answer == "":
+            answer = "n"
+
+        if answer == 'n':
+            print "Not committing changes"
+            sys.exit(0)
+        elif answer == 'y':
+            print "Committing"
+            for remove in to_remove:
+                del transitions[remove]
+
+            edit_file = temp_transitions_file(transitions)
+            write_transitions_from_file(edit_file)
+
+            print "Done"
+        else:
+            print "WTF are you typing?"
+            sys.exit(0)
+
+################################################################################
+
+def print_info(trans, source, expected, rm, reason, packages):
+    print """Looking at transition: %s
+Source:      %s
+New Version: %s
+Responsible: %s
+Description: %s
+Blocked Packages (total: %d): %s
+""" % (trans, source, expected, rm, reason, len(packages), ", ".join(packages))
+    return
+
+################################################################################
+
+def transition_info(transitions):
+    for trans in transitions:
+        t = transitions[trans]
+        source = t["source"]
+        expected = t["new"]
+
+        # Will be None if nothing is in testing.
+        current = database.get_suite_version(source, "testing")
+
+        print_info(trans, source, expected, t["rm"], t["reason"], t["packages"])
+
+        if current == None:
+            # No package in testing
+            print "Transition source %s not in testing, transition still ongoing." % (source)
+        else:
+            compare = apt_pkg.VersionCompare(current, expected)
+            print "Apt compare says: %s" % (compare)
+            if compare < 0:
+                # This is still valid, the current version in database is older than
+                # the new version we wait for
+                print "This transition is still ongoing, we currently have version %s" % (current)
+            else:
+                print "This transition is over, the target package reached testing, should be removed"
+                print "%s wanted version: %s, has %s" % (source, expected, current)
+        print "-------------------------------------------------------------------------"
+
+################################################################################
+
+def main():
+    global Cnf
+
+    #####################################
+    #### This can run within sudo !! ####
+    #####################################
+    init()
+
+    # Check if there is a file defined (and existant)
+    transpath = Cnf.get("Dinstall::Reject::ReleaseTransitions", "")
+    if transpath == "":
+        utils.warn("Dinstall::Reject::ReleaseTransitions not defined")
+        sys.exit(1)
+    if not os.path.exists(transpath):
+        utils.warn("ReleaseTransitions file, %s, not found." %
+                          (Cnf["Dinstall::Reject::ReleaseTransitions"]))
+        sys.exit(1)
+    # Also check if our temp directory is defined and existant
+    temppath = Cnf.get("Transitions::TempPath", "")
+    if temppath == "":
+        utils.warn("Transitions::TempPath not defined")
+        sys.exit(1)
+    if not os.path.exists(temppath):
+        utils.warn("Temporary path %s not found." %
+                          (Cnf["Transitions::TempPath"]))
+        sys.exit(1)
+
+    if Options["import"]:
+        try:
+            write_transitions_from_file(Options["import"])
+        except TransitionsError, m:
+            print m
+            sys.exit(2)
+        sys.exit(0)
+    ##############################################
+    #### Up to here it can run within sudo !! ####
+    ##############################################
+
+    # Parse the yaml file
+    transitions = load_transitions(transpath)
+    if transitions == None:
+        # Something very broken with the transitions, exit
+        utils.warn("Could not parse existing transitions file. Aborting.")
+        sys.exit(2)
+
+    if Options["edit"]:
+        # Let's edit the transitions file
+        edit_transitions()
+    elif Options["check"]:
+        # Check and remove outdated transitions
+        check_transitions(transitions)
+    else:
+        # Output information about the currently defined transitions.
+        print "Currently defined transitions:"
+        transition_info(transitions)
+
+    sys.exit(0)
+
+################################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/daklib/__init__.py b/daklib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/daklib/dak_exceptions.py b/daklib/dak_exceptions.py
new file mode 100644 (file)
index 0000000..4e79546
--- /dev/null
@@ -0,0 +1,67 @@
+# Exception classes used in dak
+
+# Copyright (C) 2008  Mark Hymers <mhy@debian.org>
+
+################################################################################
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+class DakError(Exception):
+    """Base class for all simple errors in this module.
+
+    Attributes:
+
+       message -- explanation of the error
+    """
+
+    def __init__(self, message=""):
+        self.args = str(message)
+        self.message = str(message)
+
+    def __str__(self):
+        return self.message
+
+__all__ = ['DakError']
+
+dakerrors = {
+    "ParseMaintError":     """Exception raised for errors in parsing a maintainer field.""",
+    "ParseChangesError":   """Exception raised for errors in parsing a changes file.""",
+    "InvalidDscError":     """Exception raised for invalid dsc files.""",
+    "UnknownFormatError":  """Exception raised for unknown Format: lines in changes files.""",
+    "NoFilesFieldError":   """Exception raised for missing files field in dsc/changes.""",
+    "CantOpenError":       """Exception raised when files can't be opened.""",
+    "CantOverwriteError":  """Exception raised when files can't be overwritten.""",
+    "FileExistsError":     """Exception raised when destination file exists.""",
+    "SendmailFailedError": """Exception raised when Sendmail invocation failed.""",
+    "NoFreeFilenameError": """Exception raised when no alternate filename was found.""",
+    "TransitionsError":    """Exception raised when transitions file can't be parsed.""",
+    "NoSourceFieldError":  """Exception raised - we cant find the source - wtf?"""
+}
+
+def construct_dak_exception(name, description):
+    class Er(DakError):
+        __doc__ = description
+    setattr(Er, "__name__", name)
+    return Er
+
+for e in dakerrors.keys():
+    globals()[e] = construct_dak_exception(e, dakerrors[e])
+    __all__ += [e]
+
+
+
+################################################################################
diff --git a/daklib/database.py b/daklib/database.py
new file mode 100755 (executable)
index 0000000..5c7bd83
--- /dev/null
@@ -0,0 +1,399 @@
+#!/usr/bin/env python
+
+# DB access fucntions
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import sys, time, types
+
+################################################################################
+
+Cnf = None
+projectB = None
+suite_id_cache = {}
+section_id_cache = {}
+priority_id_cache = {}
+override_type_id_cache = {}
+architecture_id_cache = {}
+archive_id_cache = {}
+component_id_cache = {}
+location_id_cache = {}
+maintainer_id_cache = {}
+keyring_id_cache = {}
+source_id_cache = {}
+files_id_cache = {}
+maintainer_cache = {}
+fingerprint_id_cache = {}
+queue_id_cache = {}
+uid_id_cache = {}
+suite_version_cache = {}
+
+################################################################################
+
+def init (config, sql):
+    global Cnf, projectB
+
+    Cnf = config
+    projectB = sql
+
+
+def do_query(q):
+    sys.stderr.write("query: \"%s\" ... " % (q))
+    before = time.time()
+    r = projectB.query(q)
+    time_diff = time.time()-before
+    sys.stderr.write("took %.3f seconds.\n" % (time_diff))
+    if type(r) is int:
+        sys.stderr.write("int result: %s\n" % (r))
+    elif type(r) is types.NoneType:
+        sys.stderr.write("result: None\n")
+    else:
+        sys.stderr.write("pgresult: %s\n" % (r.getresult()))
+    return r
+
+################################################################################
+
+def get_suite_id (suite):
+    global suite_id_cache
+
+    if suite_id_cache.has_key(suite):
+        return suite_id_cache[suite]
+
+    q = projectB.query("SELECT id FROM suite WHERE suite_name = '%s'" % (suite))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    suite_id = ql[0][0]
+    suite_id_cache[suite] = suite_id
+
+    return suite_id
+
+def get_section_id (section):
+    global section_id_cache
+
+    if section_id_cache.has_key(section):
+        return section_id_cache[section]
+
+    q = projectB.query("SELECT id FROM section WHERE section = '%s'" % (section))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    section_id = ql[0][0]
+    section_id_cache[section] = section_id
+
+    return section_id
+
+def get_priority_id (priority):
+    global priority_id_cache
+
+    if priority_id_cache.has_key(priority):
+        return priority_id_cache[priority]
+
+    q = projectB.query("SELECT id FROM priority WHERE priority = '%s'" % (priority))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    priority_id = ql[0][0]
+    priority_id_cache[priority] = priority_id
+
+    return priority_id
+
+def get_override_type_id (type):
+    global override_type_id_cache
+
+    if override_type_id_cache.has_key(type):
+        return override_type_id_cache[type]
+
+    q = projectB.query("SELECT id FROM override_type WHERE type = '%s'" % (type))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    override_type_id = ql[0][0]
+    override_type_id_cache[type] = override_type_id
+
+    return override_type_id
+
+def get_architecture_id (architecture):
+    global architecture_id_cache
+
+    if architecture_id_cache.has_key(architecture):
+        return architecture_id_cache[architecture]
+
+    q = projectB.query("SELECT id FROM architecture WHERE arch_string = '%s'" % (architecture))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    architecture_id = ql[0][0]
+    architecture_id_cache[architecture] = architecture_id
+
+    return architecture_id
+
+def get_archive_id (archive):
+    global archive_id_cache
+
+    archive = archive.lower()
+
+    if archive_id_cache.has_key(archive):
+        return archive_id_cache[archive]
+
+    q = projectB.query("SELECT id FROM archive WHERE lower(name) = '%s'" % (archive))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    archive_id = ql[0][0]
+    archive_id_cache[archive] = archive_id
+
+    return archive_id
+
+def get_component_id (component):
+    global component_id_cache
+
+    component = component.lower()
+
+    if component_id_cache.has_key(component):
+        return component_id_cache[component]
+
+    q = projectB.query("SELECT id FROM component WHERE lower(name) = '%s'" % (component))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    component_id = ql[0][0]
+    component_id_cache[component] = component_id
+
+    return component_id
+
+def get_location_id (location, component, archive):
+    global location_id_cache
+
+    cache_key = location + '_' + component + '_' + location
+    if location_id_cache.has_key(cache_key):
+        return location_id_cache[cache_key]
+
+    archive_id = get_archive_id (archive)
+    if component != "":
+        component_id = get_component_id (component)
+        if component_id != -1:
+            q = projectB.query("SELECT id FROM location WHERE path = '%s' AND component = %d AND archive = %d" % (location, component_id, archive_id))
+    else:
+        q = projectB.query("SELECT id FROM location WHERE path = '%s' AND archive = %d" % (location, archive_id))
+    ql = q.getresult()
+    if not ql:
+        return -1
+
+    location_id = ql[0][0]
+    location_id_cache[cache_key] = location_id
+
+    return location_id
+
+def get_source_id (source, version):
+    global source_id_cache
+
+    cache_key = source + '_' + version + '_'
+    if source_id_cache.has_key(cache_key):
+        return source_id_cache[cache_key]
+
+    q = projectB.query("SELECT id FROM source s WHERE s.source = '%s' AND s.version = '%s'" % (source, version))
+
+    if not q.getresult():
+        return None
+
+    source_id = q.getresult()[0][0]
+    source_id_cache[cache_key] = source_id
+
+    return source_id
+
+def get_suite_version(source, suite):
+    global suite_version_cache
+    cache_key = "%s_%s" % (source, suite)
+
+    if suite_version_cache.has_key(cache_key):
+        return suite_version_cache[cache_key]
+
+    q = projectB.query("""
+    SELECT s.version FROM source s, suite su, src_associations sa
+    WHERE sa.source=s.id
+      AND sa.suite=su.id
+      AND su.suite_name='%s'
+      AND s.source='%s'"""
+                              % (suite, source))
+
+    if not q.getresult():
+        return None
+
+    version = q.getresult()[0][0]
+    suite_version_cache[cache_key] = version
+
+    return version
+
+################################################################################
+
+def get_or_set_maintainer_id (maintainer):
+    global maintainer_id_cache
+
+    if maintainer_id_cache.has_key(maintainer):
+        return maintainer_id_cache[maintainer]
+
+    q = projectB.query("SELECT id FROM maintainer WHERE name = '%s'" % (maintainer))
+    if not q.getresult():
+        projectB.query("INSERT INTO maintainer (name) VALUES ('%s')" % (maintainer))
+        q = projectB.query("SELECT id FROM maintainer WHERE name = '%s'" % (maintainer))
+    maintainer_id = q.getresult()[0][0]
+    maintainer_id_cache[maintainer] = maintainer_id
+
+    return maintainer_id
+
+################################################################################
+
+def get_or_set_keyring_id (keyring):
+    global keyring_id_cache
+
+    if keyring_id_cache.has_key(keyring):
+        return keyring_id_cache[keyring]
+
+    q = projectB.query("SELECT id FROM keyrings WHERE name = '%s'" % (keyring))
+    if not q.getresult():
+        projectB.query("INSERT INTO keyrings (name) VALUES ('%s')" % (keyring))
+        q = projectB.query("SELECT id FROM keyrings WHERE name = '%s'" % (keyring))
+    keyring_id = q.getresult()[0][0]
+    keyring_id_cache[keyring] = keyring_id
+
+    return keyring_id
+
+################################################################################
+
+def get_or_set_uid_id (uid):
+    global uid_id_cache
+
+    if uid_id_cache.has_key(uid):
+        return uid_id_cache[uid]
+
+    q = projectB.query("SELECT id FROM uid WHERE uid = '%s'" % (uid))
+    if not q.getresult():
+        projectB.query("INSERT INTO uid (uid) VALUES ('%s')" % (uid))
+        q = projectB.query("SELECT id FROM uid WHERE uid = '%s'" % (uid))
+    uid_id = q.getresult()[0][0]
+    uid_id_cache[uid] = uid_id
+
+    return uid_id
+
+################################################################################
+
+def get_or_set_fingerprint_id (fingerprint):
+    global fingerprint_id_cache
+
+    if fingerprint_id_cache.has_key(fingerprint):
+        return fingerprint_id_cache[fingerprint]
+
+    q = projectB.query("SELECT id FROM fingerprint WHERE fingerprint = '%s'" % (fingerprint))
+    if not q.getresult():
+        projectB.query("INSERT INTO fingerprint (fingerprint) VALUES ('%s')" % (fingerprint))
+        q = projectB.query("SELECT id FROM fingerprint WHERE fingerprint = '%s'" % (fingerprint))
+    fingerprint_id = q.getresult()[0][0]
+    fingerprint_id_cache[fingerprint] = fingerprint_id
+
+    return fingerprint_id
+
+################################################################################
+
+def get_files_id (filename, size, md5sum, location_id):
+    global files_id_cache
+
+    cache_key = "%s_%d" % (filename, location_id)
+
+    if files_id_cache.has_key(cache_key):
+        return files_id_cache[cache_key]
+
+    size = int(size)
+    q = projectB.query("SELECT id, size, md5sum FROM files WHERE filename = '%s' AND location = %d" % (filename, location_id))
+    ql = q.getresult()
+    if ql:
+        if len(ql) != 1:
+            return -1
+        ql = ql[0]
+        orig_size = int(ql[1])
+        orig_md5sum = ql[2]
+        if orig_size != size or orig_md5sum != md5sum:
+            return -2
+        files_id_cache[cache_key] = ql[0]
+        return files_id_cache[cache_key]
+    else:
+        return None
+
+################################################################################
+
+def get_or_set_queue_id (queue):
+    global queue_id_cache
+
+    if queue_id_cache.has_key(queue):
+        return queue_id_cache[queue]
+
+    q = projectB.query("SELECT id FROM queue WHERE queue_name = '%s'" % (queue))
+    if not q.getresult():
+        projectB.query("INSERT INTO queue (queue_name) VALUES ('%s')" % (queue))
+        q = projectB.query("SELECT id FROM queue WHERE queue_name = '%s'" % (queue))
+    queue_id = q.getresult()[0][0]
+    queue_id_cache[queue] = queue_id
+
+    return queue_id
+
+################################################################################
+
+def set_files_id (filename, size, md5sum, sha1sum, sha256sum, location_id):
+    global files_id_cache
+
+    projectB.query("INSERT INTO files (filename, size, md5sum, sha1sum, sha256sum, location) VALUES ('%s', %d, '%s', '%s', '%s', %d)" % (filename, long(size), md5sum, sha1sum, sha256sum, location_id))
+
+    return get_files_id (filename, size, md5sum, location_id)
+
+    ### currval has issues with postgresql 7.1.3 when the table is big
+    ### it was taking ~3 seconds to return on auric which is very Not
+    ### Cool(tm).
+    ##
+    ##q = projectB.query("SELECT id FROM files WHERE id = currval('files_id_seq')")
+    ##ql = q.getresult()[0]
+    ##cache_key = "%s_%d" % (filename, location_id)
+    ##files_id_cache[cache_key] = ql[0]
+    ##return files_id_cache[cache_key]
+
+################################################################################
+
+def get_maintainer (maintainer_id):
+    global maintainer_cache
+
+    if not maintainer_cache.has_key(maintainer_id):
+        q = projectB.query("SELECT name FROM maintainer WHERE id = %s" % (maintainer_id))
+        maintainer_cache[maintainer_id] = q.getresult()[0][0]
+
+    return maintainer_cache[maintainer_id]
+
+################################################################################
+
+def get_suites(pkgname, src=False):
+    if src:
+        sql = "select suite_name from source, src_associations,suite where source.id=src_associations.source and source.source='%s' and src_associations.suite = suite.id"%pkgname
+    else:
+        sql = "select suite_name from binaries, bin_associations,suite where binaries.id=bin_associations.bin and  package='%s' and bin_associations.suite = suite.id"%pkgname
+    q = projectB.query(sql)
+    return map(lambda x: x[0], q.getresult())
diff --git a/daklib/extensions.py b/daklib/extensions.py
new file mode 100644 (file)
index 0000000..9befa7b
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+# Utility functions for extensions
+# Copyright (C) 2008 Anthony Towns <ajt@dbeian.org>
+
+################################################################################
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+dak_functions_to_replace = {}
+dak_replaced_functions = {}
+
+def replace_dak_function(module,name):
+    """Decorator to make a function replace a standard dak function
+       in a given module. The replaced function will be provided as
+       the first argument."""
+
+    def x(f):
+        def myfunc(*a,**kw):
+            global replaced_funcs
+            f(dak_replaced_functions[name], *a, **kw)
+        myfunc.__name__ = f.__name__
+        myfunc.__doc__ = f.__doc__
+        myfunc.__dict__.update(f.__dict__)
+
+        fnname = "%s:%s" % (module, name)
+        if fnname in dak_functions_to_replace:
+            raise Exception, \
+                "%s in %s already marked to be replaced" % (name, module)
+        dak_functions_to_replace["%s:%s" % (module,name)] = myfunc
+        return f
+    return x
+
+################################################################################
+
+def init(name, module, userext):
+    global dak_replaced_functions
+
+    # This bit should be done automatically too
+    dak_replaced_functions = {}
+    for f,newfunc in dak_functions_to_replace.iteritems():
+        m,f = f.split(":",1)
+        if len(f) > 0 and m == name:
+            dak_replaced_functions[f] = module.__dict__[f]
+            module.__dict__[f] = newfunc
diff --git a/daklib/logging.py b/daklib/logging.py
new file mode 100644 (file)
index 0000000..f0dcd9c
--- /dev/null
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+
+# Logging functions
+# Copyright (C) 2001, 2002, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import os, pwd, time, sys
+import utils
+
+################################################################################
+
+class Logger:
+    "Logger object"
+    Cnf = None
+    logfile = None
+    program = None
+
+    def __init__ (self, Cnf, program, debug=0):
+        "Initialize a new Logger object"
+        self.Cnf = Cnf
+        self.program = program
+        # Create the log directory if it doesn't exist
+        logdir = Cnf["Dir::Log"]
+        if not os.path.exists(logdir):
+            umask = os.umask(00000)
+            os.makedirs(logdir, 02775)
+            os.umask(umask)
+        # Open the logfile
+        logfilename = "%s/%s" % (logdir, time.strftime("%Y-%m"))
+        logfile = None
+        if debug:
+            logfile = sys.stderr
+        else:
+            umask = os.umask(00002)
+            logfile = utils.open_file(logfilename, 'a')
+            os.umask(umask)
+        self.logfile = logfile
+        # Log the start of the program
+        user = pwd.getpwuid(os.getuid())[0]
+        self.log(["program start", user])
+
+    def log (self, details):
+        "Log an event"
+        # Prepend the timestamp and program name
+        details.insert(0, self.program)
+        timestamp = time.strftime("%Y%m%d%H%M%S")
+        details.insert(0, timestamp)
+        # Force the contents of the list to be string.join-able
+        details = [ str(i) for i in details ]
+        # Write out the log in TSV
+        self.logfile.write("|".join(details)+'\n')
+        # Flush the output to enable tail-ing
+        self.logfile.flush()
+
+    def close (self):
+        "Close a Logger object"
+        self.log(["program end"])
+        self.logfile.flush()
+        self.logfile.close()
diff --git a/daklib/queue.py b/daklib/queue.py
new file mode 100755 (executable)
index 0000000..1fcfeaa
--- /dev/null
@@ -0,0 +1,1104 @@
+#!/usr/bin/env python
+# vim:set et sw=4:
+
+# Queue utility functions for dak
+# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+###############################################################################
+
+import cPickle, errno, os, pg, re, stat, sys, time
+import apt_inst, apt_pkg
+import utils, database
+from dak_exceptions import *
+
+from types import *
+
+###############################################################################
+
+re_isanum = re.compile (r"^\d+$")
+re_default_answer = re.compile(r"\[(.*)\]")
+re_fdnic = re.compile(r"\n\n")
+re_bin_only_nmu = re.compile(r"\+b\d+$")
+
+################################################################################
+
+# Determine what parts in a .changes are NEW
+
+def determine_new(changes, files, projectB, warn=1):
+    new = {}
+
+    # Build up a list of potentially new things
+    for file_entry in files.keys():
+        f = files[file_entry]
+        # Skip byhand elements
+        if f["type"] == "byhand":
+            continue
+        pkg = f["package"]
+        priority = f["priority"]
+        section = f["section"]
+        file_type = get_type(f)
+        component = f["component"]
+
+        if file_type == "dsc":
+            priority = "source"
+        if not new.has_key(pkg):
+            new[pkg] = {}
+            new[pkg]["priority"] = priority
+            new[pkg]["section"] = section
+            new[pkg]["type"] = file_type
+            new[pkg]["component"] = component
+            new[pkg]["files"] = []
+        else:
+            old_type = new[pkg]["type"]
+            if old_type != file_type:
+                # source gets trumped by deb or udeb
+                if old_type == "dsc":
+                    new[pkg]["priority"] = priority
+                    new[pkg]["section"] = section
+                    new[pkg]["type"] = file_type
+                    new[pkg]["component"] = component
+        new[pkg]["files"].append(file_entry)
+        if f.has_key("othercomponents"):
+            new[pkg]["othercomponents"] = f["othercomponents"]
+
+    for suite in changes["suite"].keys():
+        suite_id = database.get_suite_id(suite)
+        for pkg in new.keys():
+            component_id = database.get_component_id(new[pkg]["component"])
+            type_id = database.get_override_type_id(new[pkg]["type"])
+            q = projectB.query("SELECT package FROM override WHERE package = '%s' AND suite = %s AND component = %s AND type = %s" % (pkg, suite_id, component_id, type_id))
+            ql = q.getresult()
+            if ql:
+                for file_entry in new[pkg]["files"]:
+                    if files[file_entry].has_key("new"):
+                        del files[file_entry]["new"]
+                del new[pkg]
+
+    if warn:
+        if changes["suite"].has_key("stable"):
+            print "WARNING: overrides will be added for stable!"
+            if changes["suite"].has_key("oldstable"):
+                print "WARNING: overrides will be added for OLDstable!"
+        for pkg in new.keys():
+            if new[pkg].has_key("othercomponents"):
+                print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"])
+
+    return new
+
+################################################################################
+
+def get_type(f):
+    # Determine the type
+    if f.has_key("dbtype"):
+        file_type = f["dbtype"]
+    elif f["type"] in [ "orig.tar.gz", "orig.tar.bz2", "tar.gz", "tar.bz2", "diff.gz", "diff.bz2", "dsc" ]:
+        file_type = "dsc"
+    else:
+        utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (file_type))
+
+    # Validate the override type
+    type_id = database.get_override_type_id(file_type)
+    if type_id == -1:
+        utils.fubar("invalid type (%s) for new.  Say wha?" % (file_type))
+
+    return file_type
+
+################################################################################
+
+# check if section/priority values are valid
+
+def check_valid(new):
+    for pkg in new.keys():
+        section = new[pkg]["section"]
+        priority = new[pkg]["priority"]
+        file_type = new[pkg]["type"]
+        new[pkg]["section id"] = database.get_section_id(section)
+        new[pkg]["priority id"] = database.get_priority_id(new[pkg]["priority"])
+        # Sanity checks
+        di = section.find("debian-installer") != -1
+        if (di and file_type not in ("udeb", "dsc")) or (not di and file_type == "udeb"):
+            new[pkg]["section id"] = -1
+        if (priority == "source" and file_type != "dsc") or \
+           (priority != "source" and file_type == "dsc"):
+            new[pkg]["priority id"] = -1
+
+
+###############################################################################
+
+# Convenience wrapper to carry around all the package information in
+
+class Pkg:
+    def __init__(self, **kwds):
+        self.__dict__.update(kwds)
+
+    def update(self, **kwds):
+        self.__dict__.update(kwds)
+
+###############################################################################
+
+class Upload:
+
+    def __init__(self, Cnf):
+        self.Cnf = Cnf
+        self.accept_count = 0
+        self.accept_bytes = 0L
+        self.pkg = Pkg(changes = {}, dsc = {}, dsc_files = {}, files = {},
+                       legacy_source_untouchable = {})
+
+        # Initialize the substitution template mapping global
+        Subst = self.Subst = {}
+        Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
+        Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]
+        Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
+        Subst["__DAK_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
+
+        self.projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+        database.init(Cnf, self.projectB)
+
+    ###########################################################################
+
+    def init_vars (self):
+        self.pkg.changes.clear()
+        self.pkg.dsc.clear()
+        self.pkg.files.clear()
+        self.pkg.dsc_files.clear()
+        self.pkg.legacy_source_untouchable.clear()
+        self.pkg.orig_tar_id = None
+        self.pkg.orig_tar_location = ""
+        self.pkg.orig_tar_gz = None
+
+    ###########################################################################
+
+    def update_vars (self):
+        dump_filename = self.pkg.changes_file[:-8]+".dak"
+        dump_file = utils.open_file(dump_filename)
+        p = cPickle.Unpickler(dump_file)
+
+        self.pkg.changes.update(p.load())
+        self.pkg.dsc.update(p.load())
+        self.pkg.files.update(p.load())
+        self.pkg.dsc_files.update(p.load())
+        self.pkg.legacy_source_untouchable.update(p.load())
+
+        self.pkg.orig_tar_id = p.load()
+        self.pkg.orig_tar_location = p.load()
+
+        dump_file.close()
+
+    ###########################################################################
+
+    # This could just dump the dictionaries as is, but I'd like to
+    # avoid this so there's some idea of what process-accepted &
+    # process-new use from process-unchecked
+
+    def dump_vars(self, dest_dir):
+
+        changes = self.pkg.changes
+        dsc = self.pkg.dsc
+        files = self.pkg.files
+        dsc_files = self.pkg.dsc_files
+        legacy_source_untouchable = self.pkg.legacy_source_untouchable
+        orig_tar_id = self.pkg.orig_tar_id
+        orig_tar_location = self.pkg.orig_tar_location
+
+        dump_filename = os.path.join(dest_dir,self.pkg.changes_file[:-8] + ".dak")
+        dump_file = utils.open_file(dump_filename, 'w')
+        try:
+            os.chmod(dump_filename, 0664)
+        except OSError, e:
+            # chmod may fail when the dumpfile is not owned by the user
+            # invoking dak (like e.g. when NEW is processed by a member
+            # of ftpteam)
+            if errno.errorcode[e.errno] == 'EPERM':
+                perms = stat.S_IMODE(os.stat(dump_filename)[stat.ST_MODE])
+                # security precaution, should never happen unless a weird
+                # umask is set anywhere
+                if perms & stat.S_IWOTH:
+                    utils.fubar("%s is world writable and chmod failed." % \
+                        (dump_filename,))
+                # ignore the failed chmod otherwise as the file should
+                # already have the right privileges and is just, at worst,
+                # unreadable for world
+            else:
+                raise
+
+        p = cPickle.Pickler(dump_file, 1)
+        d_changes = {}
+        d_dsc = {}
+        d_files = {}
+        d_dsc_files = {}
+
+        ## files
+        for file_entry in files.keys():
+            d_files[file_entry] = {}
+            for i in [ "package", "version", "architecture", "type", "size",
+                       "md5sum", "sha1sum", "sha256sum", "component",
+                       "location id", "source package", "source version",
+                       "maintainer", "dbtype", "files id", "new",
+                       "section", "priority", "othercomponents",
+                       "pool name", "original component" ]:
+                if files[file_entry].has_key(i):
+                    d_files[file_entry][i] = files[file_entry][i]
+        ## changes
+        # Mandatory changes fields
+        for i in [ "distribution", "source", "architecture", "version",
+                   "maintainer", "urgency", "fingerprint", "changedby822",
+                   "changedby2047", "changedbyname", "maintainer822",
+                   "maintainer2047", "maintainername", "maintaineremail",
+                   "closes", "changes" ]:
+            d_changes[i] = changes[i]
+        # Optional changes fields
+        for i in [ "changed-by", "filecontents", "format", "process-new note", "adv id", "distribution-version",
+                   "sponsoremail" ]:
+            if changes.has_key(i):
+                d_changes[i] = changes[i]
+        ## dsc
+        for i in [ "source", "version", "maintainer", "fingerprint",
+                   "uploaders", "bts changelog", "dm-upload-allowed" ]:
+            if dsc.has_key(i):
+                d_dsc[i] = dsc[i]
+        ## dsc_files
+        for file_entry in dsc_files.keys():
+            d_dsc_files[file_entry] = {}
+            # Mandatory dsc_files fields
+            for i in [ "size", "md5sum" ]:
+                d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
+            # Optional dsc_files fields
+            for i in [ "files id" ]:
+                if dsc_files[file_entry].has_key(i):
+                    d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
+
+        for i in [ d_changes, d_dsc, d_files, d_dsc_files,
+                   legacy_source_untouchable, orig_tar_id, orig_tar_location ]:
+            p.dump(i)
+        dump_file.close()
+
+    ###########################################################################
+
+    # Set up the per-package template substitution mappings
+
+    def update_subst (self, reject_message = ""):
+        Subst = self.Subst
+        changes = self.pkg.changes
+        # If 'dak process-unchecked' crashed out in the right place, architecture may still be a string.
+        if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
+            changes["architecture"] = { "Unknown" : "" }
+        # and maintainer2047 may not exist.
+        if not changes.has_key("maintainer2047"):
+            changes["maintainer2047"] = self.Cnf["Dinstall::MyEmailAddress"]
+
+        Subst["__ARCHITECTURE__"] = " ".join(changes["architecture"].keys())
+        Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file)
+        Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "")
+
+        # For source uploads the Changed-By field wins; otherwise Maintainer wins.
+        if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
+            Subst["__MAINTAINER_FROM__"] = changes["changedby2047"]
+            Subst["__MAINTAINER_TO__"] = "%s, %s" % (changes["changedby2047"],
+                                                     changes["maintainer2047"])
+            Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown")
+        else:
+            Subst["__MAINTAINER_FROM__"] = changes["maintainer2047"]
+            Subst["__MAINTAINER_TO__"] = changes["maintainer2047"]
+            Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown")
+
+        if "sponsoremail" in changes:
+            Subst["__MAINTAINER_TO__"] += ", %s"%changes["sponsoremail"]
+
+        if self.Cnf.has_key("Dinstall::TrackingServer") and changes.has_key("source"):
+            Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (changes["source"], self.Cnf["Dinstall::TrackingServer"])
+
+        # Apply any global override of the Maintainer field
+        if self.Cnf.get("Dinstall::OverrideMaintainer"):
+            Subst["__MAINTAINER_TO__"] = self.Cnf["Dinstall::OverrideMaintainer"]
+            Subst["__MAINTAINER_FROM__"] = self.Cnf["Dinstall::OverrideMaintainer"]
+
+        Subst["__REJECT_MESSAGE__"] = reject_message
+        Subst["__SOURCE__"] = changes.get("source", "Unknown")
+        Subst["__VERSION__"] = changes.get("version", "Unknown")
+
+    ###########################################################################
+
+    def build_summaries(self):
+        changes = self.pkg.changes
+        files = self.pkg.files
+
+        byhand = summary = new = ""
+
+        # changes["distribution"] may not exist in corner cases
+        # (e.g. unreadable changes files)
+        if not changes.has_key("distribution") or not isinstance(changes["distribution"], DictType):
+            changes["distribution"] = {}
+
+        override_summary ="";
+        file_keys = files.keys()
+        file_keys.sort()
+        for file_entry in file_keys:
+            if files[file_entry].has_key("byhand"):
+                byhand = 1
+                summary += file_entry + " byhand\n"
+            elif files[file_entry].has_key("new"):
+                new = 1
+                summary += "(new) %s %s %s\n" % (file_entry, files[file_entry]["priority"], files[file_entry]["section"])
+                if files[file_entry].has_key("othercomponents"):
+                    summary += "WARNING: Already present in %s distribution.\n" % (files[file_entry]["othercomponents"])
+                if files[file_entry]["type"] == "deb":
+                    deb_fh = utils.open_file(file_entry)
+                    summary += apt_pkg.ParseSection(apt_inst.debExtractControl(deb_fh))["Description"] + '\n'
+                    deb_fh.close()
+            else:
+                files[file_entry]["pool name"] = utils.poolify (changes.get("source",""), files[file_entry]["component"])
+                destination = self.Cnf["Dir::PoolRoot"] + files[file_entry]["pool name"] + file_entry
+                summary += file_entry + "\n  to " + destination + "\n"
+                if not files[file_entry].has_key("type"):
+                    files[file_entry]["type"] = "unknown"
+                if files[file_entry]["type"] in ["deb", "udeb", "dsc"]:
+                    # (queue/unchecked), there we have override entries already, use them
+                    # (process-new), there we dont have override entries, use the newly generated ones.
+                    override_prio = files[file_entry].get("override priority", files[file_entry]["priority"])
+                    override_sect = files[file_entry].get("override section", files[file_entry]["section"])
+                    override_summary += "%s - %s %s\n" % (file_entry, override_prio, override_sect)
+
+        short_summary = summary
+
+        # This is for direport's benefit...
+        f = re_fdnic.sub("\n .\n", changes.get("changes",""))
+
+        if byhand or new:
+            summary += "Changes: " + f
+
+        summary += "\n\nOverride entries for your package:\n" + override_summary + "\n"
+
+        summary += self.announce(short_summary, 0)
+
+        return (summary, short_summary)
+
+    ###########################################################################
+
+    def close_bugs (self, summary, action):
+        changes = self.pkg.changes
+        Subst = self.Subst
+        Cnf = self.Cnf
+
+        bugs = changes["closes"].keys()
+
+        if not bugs:
+            return summary
+
+        bugs.sort()
+        summary += "Closing bugs: "
+        for bug in bugs:
+            summary += "%s " % (bug)
+            if action:
+                Subst["__BUG_NUMBER__"] = bug
+                if changes["distribution"].has_key("stable"):
+                    Subst["__STABLE_WARNING__"] = """
+Note that this package is not part of the released stable Debian
+distribution.  It may have dependencies on other unreleased software,
+or other instabilities.  Please take care if you wish to install it.
+The update will eventually make its way into the next released Debian
+distribution."""
+                else:
+                    Subst["__STABLE_WARNING__"] = ""
+                    mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.bug-close")
+                    utils.send_mail (mail_message)
+        if action:
+            self.Logger.log(["closing bugs"]+bugs)
+        summary += "\n"
+
+        return summary
+
+    ###########################################################################
+
+    def announce (self, short_summary, action):
+        Subst = self.Subst
+        Cnf = self.Cnf
+        changes = self.pkg.changes
+
+        # Only do announcements for source uploads with a recent dpkg-dev installed
+        if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
+            return ""
+
+        lists_done = {}
+        summary = ""
+        Subst["__SHORT_SUMMARY__"] = short_summary
+
+        for dist in changes["distribution"].keys():
+            announce_list = Cnf.Find("Suite::%s::Announce" % (dist))
+            if announce_list == "" or lists_done.has_key(announce_list):
+                continue
+            lists_done[announce_list] = 1
+            summary += "Announcing to %s\n" % (announce_list)
+
+            if action:
+                Subst["__ANNOUNCE_LIST_ADDRESS__"] = announce_list
+                if Cnf.get("Dinstall::TrackingServer") and changes["architecture"].has_key("source"):
+                    Subst["__ANNOUNCE_LIST_ADDRESS__"] = Subst["__ANNOUNCE_LIST_ADDRESS__"] + "\nBcc: %s@%s" % (changes["source"], Cnf["Dinstall::TrackingServer"])
+                mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.announce")
+                utils.send_mail (mail_message)
+
+        if Cnf.FindB("Dinstall::CloseBugs"):
+            summary = self.close_bugs(summary, action)
+
+        return summary
+
+    ###########################################################################
+
+    def accept (self, summary, short_summary):
+        Cnf = self.Cnf
+        Subst = self.Subst
+        files = self.pkg.files
+        changes = self.pkg.changes
+        changes_file = self.pkg.changes_file
+        dsc = self.pkg.dsc
+
+        print "Accepting."
+        self.Logger.log(["Accepting changes",changes_file])
+
+        self.dump_vars(Cnf["Dir::Queue::Accepted"])
+
+        # Move all the files into the accepted directory
+        utils.move(changes_file, Cnf["Dir::Queue::Accepted"])
+        file_keys = files.keys()
+        for file_entry in file_keys:
+            utils.move(file_entry, Cnf["Dir::Queue::Accepted"])
+            self.accept_bytes += float(files[file_entry]["size"])
+        self.accept_count += 1
+
+        # Send accept mail, announce to lists, close bugs and check for
+        # override disparities
+        if not Cnf["Dinstall::Options::No-Mail"]:
+            Subst["__SUITE__"] = ""
+            Subst["__SUMMARY__"] = summary
+            mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.accepted")
+            utils.send_mail(mail_message)
+            self.announce(short_summary, 1)
+
+
+        ## Helper stuff for DebBugs Version Tracking
+        if Cnf.Find("Dir::Queue::BTSVersionTrack"):
+            # ??? once queue/* is cleared on *.d.o and/or reprocessed
+            # the conditionalization on dsc["bts changelog"] should be
+            # dropped.
+
+            # Write out the version history from the changelog
+            if changes["architecture"].has_key("source") and \
+               dsc.has_key("bts changelog"):
+
+                temp_filename = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"],
+                                                    dotprefix=1, perms=0644)
+                version_history = utils.open_file(temp_filename, 'w')
+                version_history.write(dsc["bts changelog"])
+                version_history.close()
+                filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
+                                      changes_file[:-8]+".versions")
+                os.rename(temp_filename, filename)
+
+            # Write out the binary -> source mapping.
+            temp_filename = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"],
+                                                dotprefix=1, perms=0644)
+            debinfo = utils.open_file(temp_filename, 'w')
+            for file_entry in file_keys:
+                f = files[file_entry]
+                if f["type"] == "deb":
+                    line = " ".join([f["package"], f["version"],
+                                     f["architecture"], f["source package"],
+                                     f["source version"]])
+                    debinfo.write(line+"\n")
+            debinfo.close()
+            filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
+                                  changes_file[:-8]+".debinfo")
+            os.rename(temp_filename, filename)
+
+        self.queue_build("accepted", Cnf["Dir::Queue::Accepted"])
+
+    ###########################################################################
+
+    def queue_build (self, queue, path):
+        Cnf = self.Cnf
+        Subst = self.Subst
+        files = self.pkg.files
+        changes = self.pkg.changes
+        changes_file = self.pkg.changes_file
+        dsc = self.pkg.dsc
+        file_keys = files.keys()
+
+        ## Special support to enable clean auto-building of queued packages
+        queue_id = database.get_or_set_queue_id(queue)
+
+        self.projectB.query("BEGIN WORK")
+        for suite in changes["distribution"].keys():
+            if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
+                continue
+            suite_id = database.get_suite_id(suite)
+            dest_dir = Cnf["Dir::QueueBuild"]
+            if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+                dest_dir = os.path.join(dest_dir, suite)
+            for file_entry in file_keys:
+                src = os.path.join(path, file_entry)
+                dest = os.path.join(dest_dir, file_entry)
+                if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+                    # Copy it since the original won't be readable by www-data
+                    utils.copy(src, dest)
+                else:
+                    # Create a symlink to it
+                    os.symlink(src, dest)
+                # Add it to the list of packages for later processing by apt-ftparchive
+                self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
+            # If the .orig.tar.gz is in the pool, create a symlink to
+            # it (if one doesn't already exist)
+            if self.pkg.orig_tar_id:
+                # Determine the .orig.tar.gz file name
+                for dsc_file in self.pkg.dsc_files.keys():
+                    if dsc_file.endswith(".orig.tar.gz"):
+                        filename = dsc_file
+                dest = os.path.join(dest_dir, filename)
+                # If it doesn't exist, create a symlink
+                if not os.path.exists(dest):
+                    # Find the .orig.tar.gz in the pool
+                    q = self.projectB.query("SELECT l.path, f.filename from location l, files f WHERE f.id = %s and f.location = l.id" % (self.pkg.orig_tar_id))
+                    ql = q.getresult()
+                    if not ql:
+                        utils.fubar("[INTERNAL ERROR] Couldn't find id %s in files table." % (self.pkg.orig_tar_id))
+                    src = os.path.join(ql[0][0], ql[0][1])
+                    os.symlink(src, dest)
+                    # Add it to the list of packages for later processing by apt-ftparchive
+                    self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
+                # if it does, update things to ensure it's not removed prematurely
+                else:
+                    self.projectB.query("UPDATE queue_build SET in_queue = 't', last_used = NULL WHERE filename = '%s' AND suite = %s" % (dest, suite_id))
+
+        self.projectB.query("COMMIT WORK")
+
+    ###########################################################################
+
+    def check_override (self):
+        Subst = self.Subst
+        changes = self.pkg.changes
+        files = self.pkg.files
+        Cnf = self.Cnf
+
+        # Abandon the check if:
+        #  a) it's a non-sourceful upload
+        #  b) override disparity checks have been disabled
+        #  c) we're not sending mail
+        if not changes["architecture"].has_key("source") or \
+           not Cnf.FindB("Dinstall::OverrideDisparityCheck") or \
+           Cnf["Dinstall::Options::No-Mail"]:
+            return
+
+        summary = ""
+        file_keys = files.keys()
+        file_keys.sort()
+        for file_entry in file_keys:
+            if not files[file_entry].has_key("new") and files[file_entry]["type"] == "deb":
+                section = files[file_entry]["section"]
+                override_section = files[file_entry]["override section"]
+                if section.lower() != override_section.lower() and section != "-":
+                    summary += "%s: package says section is %s, override says %s.\n" % (file_entry, section, override_section)
+                priority = files[file_entry]["priority"]
+                override_priority = files[file_entry]["override priority"]
+                if priority != override_priority and priority != "-":
+                    summary += "%s: package says priority is %s, override says %s.\n" % (file_entry, priority, override_priority)
+
+        if summary == "":
+            return
+
+        Subst["__SUMMARY__"] = summary
+        mail_message = utils.TemplateSubst(Subst,self.Cnf["Dir::Templates"]+"/process-unchecked.override-disparity")
+        utils.send_mail(mail_message)
+
+    ###########################################################################
+
+    def force_reject (self, files):
+        """Forcefully move files from the current directory to the
+           reject directory.  If any file already exists in the reject
+           directory it will be moved to the morgue to make way for
+           the new file."""
+
+        Cnf = self.Cnf
+
+        for file_entry in files:
+            # Skip any files which don't exist or which we don't have permission to copy.
+            if os.access(file_entry,os.R_OK) == 0:
+                continue
+            dest_file = os.path.join(Cnf["Dir::Queue::Reject"], file_entry)
+            try:
+                dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
+            except OSError, e:
+                # File exists?  Let's try and move it to the morgue
+                if errno.errorcode[e.errno] == 'EEXIST':
+                    morgue_file = os.path.join(Cnf["Dir::Morgue"],Cnf["Dir::MorgueReject"],file_entry)
+                    try:
+                        morgue_file = utils.find_next_free(morgue_file)
+                    except NoFreeFilenameError:
+                        # Something's either gone badly Pete Tong, or
+                        # someone is trying to exploit us.
+                        utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file_entry))
+                        return
+                    utils.move(dest_file, morgue_file, perms=0660)
+                    try:
+                        dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
+                    except OSError, e:
+                        # Likewise
+                        utils.warn("**WARNING** failed to claim %s in the reject directory." % (file_entry))
+                        return
+                else:
+                    raise
+            # If we got here, we own the destination file, so we can
+            # safely overwrite it.
+            utils.move(file_entry, dest_file, 1, perms=0660)
+            os.close(dest_fd)
+
+    ###########################################################################
+
+    def do_reject (self, manual = 0, reject_message = ""):
+        # If we weren't given a manual rejection message, spawn an
+        # editor so the user can add one in...
+        if manual and not reject_message:
+            temp_filename = utils.temp_filename()
+            editor = os.environ.get("EDITOR","vi")
+            answer = 'E'
+            while answer == 'E':
+                os.system("%s %s" % (editor, temp_filename))
+                temp_fh = utils.open_file(temp_filename)
+                reject_message = "".join(temp_fh.readlines())
+                temp_fh.close()
+                print "Reject message:"
+                print utils.prefix_multi_line_string(reject_message,"  ",include_blank_lines=1)
+                prompt = "[R]eject, Edit, Abandon, Quit ?"
+                answer = "XXX"
+                while prompt.find(answer) == -1:
+                    answer = utils.our_raw_input(prompt)
+                    m = re_default_answer.search(prompt)
+                    if answer == "":
+                        answer = m.group(1)
+                    answer = answer[:1].upper()
+            os.unlink(temp_filename)
+            if answer == 'A':
+                return 1
+            elif answer == 'Q':
+                sys.exit(0)
+
+        print "Rejecting.\n"
+
+        Cnf = self.Cnf
+        Subst = self.Subst
+        pkg = self.pkg
+
+        reason_filename = pkg.changes_file[:-8] + ".reason"
+        reason_filename = Cnf["Dir::Queue::Reject"] + '/' + reason_filename
+
+        # Move all the files into the reject directory
+        reject_files = pkg.files.keys() + [pkg.changes_file]
+        self.force_reject(reject_files)
+
+        # If we fail here someone is probably trying to exploit the race
+        # so let's just raise an exception ...
+        if os.path.exists(reason_filename):
+            os.unlink(reason_filename)
+        reason_fd = os.open(reason_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
+
+        if not manual:
+            Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
+            Subst["__MANUAL_REJECT_MESSAGE__"] = ""
+            Subst["__CC__"] = "X-DAK-Rejection: automatic (moo)\nX-Katie-Rejection: automatic (moo)"
+            os.write(reason_fd, reject_message)
+            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
+        else:
+            # Build up the rejection email
+            user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"])
+
+            Subst["__REJECTOR_ADDRESS__"] = user_email_address
+            Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message
+            Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
+            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
+            # Write the rejection email out as the <foo>.reason file
+            os.write(reason_fd, reject_mail_message)
+
+        os.close(reason_fd)
+
+        # Send the rejection mail if appropriate
+        if not Cnf["Dinstall::Options::No-Mail"]:
+            utils.send_mail(reject_mail_message)
+
+        self.Logger.log(["rejected", pkg.changes_file])
+        return 0
+
+    ################################################################################
+
+    # Ensure that source exists somewhere in the archive for the binary
+    # upload being processed.
+    #
+    # (1) exact match                      => 1.0-3
+    # (2) Bin-only NMU                     => 1.0-3+b1 , 1.0-3.1+b1
+
+    def source_exists (self, package, source_version, suites = ["any"]):
+        okay = 1
+        for suite in suites:
+            if suite == "any":
+                que = "SELECT s.version FROM source s WHERE s.source = '%s'" % \
+                    (package)
+            else:
+                # source must exist in suite X, or in some other suite that's
+                # mapped to X, recursively... silent-maps are counted too,
+                # unreleased-maps aren't.
+                maps = self.Cnf.ValueList("SuiteMappings")[:]
+                maps.reverse()
+                maps = [ m.split() for m in maps ]
+                maps = [ (x[1], x[2]) for x in maps
+                                if x[0] == "map" or x[0] == "silent-map" ]
+                s = [suite]
+                for x in maps:
+                    if x[1] in s and x[0] not in s:
+                        s.append(x[0])
+
+                que = "SELECT s.version FROM source s JOIN src_associations sa ON (s.id = sa.source) JOIN suite su ON (sa.suite = su.id) WHERE s.source = '%s' AND (%s)" % (package, " OR ".join(["su.suite_name = '%s'" % a for a in s]))
+            q = self.projectB.query(que)
+
+            # Reduce the query results to a list of version numbers
+            ql = [ i[0] for i in q.getresult() ]
+
+            # Try (1)
+            if source_version in ql:
+                continue
+
+            # Try (2)
+            orig_source_version = re_bin_only_nmu.sub('', source_version)
+            if orig_source_version in ql:
+                continue
+
+            # No source found...
+            okay = 0
+            break
+        return okay
+
+    ################################################################################
+
+    def in_override_p (self, package, component, suite, binary_type, file):
+        files = self.pkg.files
+
+        if binary_type == "": # must be source
+            file_type = "dsc"
+        else:
+            file_type = binary_type
+
+        # Override suite name; used for example with proposed-updates
+        if self.Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
+            suite = self.Cnf["Suite::%s::OverrideSuite" % (suite)]
+
+        # Avoid <undef> on unknown distributions
+        suite_id = database.get_suite_id(suite)
+        if suite_id == -1:
+            return None
+        component_id = database.get_component_id(component)
+        type_id = database.get_override_type_id(file_type)
+
+        q = self.projectB.query("SELECT s.section, p.priority FROM override o, section s, priority p WHERE package = '%s' AND suite = %s AND component = %s AND type = %s AND o.section = s.id AND o.priority = p.id"
+                           % (package, suite_id, component_id, type_id))
+        result = q.getresult()
+        # If checking for a source package fall back on the binary override type
+        if file_type == "dsc" and not result:
+            deb_type_id = database.get_override_type_id("deb")
+            udeb_type_id = database.get_override_type_id("udeb")
+            q = self.projectB.query("SELECT s.section, p.priority FROM override o, section s, priority p WHERE package = '%s' AND suite = %s AND component = %s AND (type = %s OR type = %s) AND o.section = s.id AND o.priority = p.id"
+                               % (package, suite_id, component_id, deb_type_id, udeb_type_id))
+            result = q.getresult()
+
+        # Remember the section and priority so we can check them later if appropriate
+        if result:
+            files[file]["override section"] = result[0][0]
+            files[file]["override priority"] = result[0][1]
+
+        return result
+
+    ################################################################################
+
+    def reject (self, str, prefix="Rejected: "):
+        if str:
+            # Unlike other rejects we add new lines first to avoid trailing
+            # new lines when this message is passed back up to a caller.
+            if self.reject_message:
+                self.reject_message += "\n"
+            self.reject_message += prefix + str
+
+    ################################################################################
+
+    def get_anyversion(self, query_result, suite):
+        anyversion=None
+        anysuite = [suite] + self.Cnf.ValueList("Suite::%s::VersionChecks::Enhances" % (suite))
+        for (v, s) in query_result:
+            if s in [ x.lower() for x in anysuite ]:
+                if not anyversion or apt_pkg.VersionCompare(anyversion, v) <= 0:
+                    anyversion=v
+        return anyversion
+
+    ################################################################################
+
+    def cross_suite_version_check(self, query_result, file, new_version,
+            sourceful=False):
+        """Ensure versions are newer than existing packages in target
+        suites and that cross-suite version checking rules as
+        set out in the conf file are satisfied."""
+
+        # Check versions for each target suite
+        for target_suite in self.pkg.changes["distribution"].keys():
+            must_be_newer_than = [ i.lower() for i in self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeNewerThan" % (target_suite)) ]
+            must_be_older_than = [ i.lower() for i in self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeOlderThan" % (target_suite)) ]
+            # Enforce "must be newer than target suite" even if conffile omits it
+            if target_suite not in must_be_newer_than:
+                must_be_newer_than.append(target_suite)
+            for entry in query_result:
+                existent_version = entry[0]
+                suite = entry[1]
+                if suite in must_be_newer_than and sourceful and \
+                   apt_pkg.VersionCompare(new_version, existent_version) < 1:
+                    self.reject("%s: old version (%s) in %s >= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
+                if suite in must_be_older_than and \
+                   apt_pkg.VersionCompare(new_version, existent_version) > -1:
+                    ch = self.pkg.changes
+                    cansave = 0
+                    if ch.get('distribution-version', {}).has_key(suite):
+                    # we really use the other suite, ignoring the conflicting one ...
+                        addsuite = ch["distribution-version"][suite]
+
+                        add_version = self.get_anyversion(query_result, addsuite)
+                        target_version = self.get_anyversion(query_result, target_suite)
+
+                        if not add_version:
+                            # not add_version can only happen if we map to a suite
+                            # that doesn't enhance the suite we're propup'ing from.
+                            # so "propup-ver x a b c; map a d" is a problem only if
+                            # d doesn't enhance a.
+                            #
+                            # i think we could always propagate in this case, rather
+                            # than complaining. either way, this isn't a REJECT issue
+                            #
+                            # And - we really should complain to the dorks who configured dak
+                            self.reject("%s is mapped to, but not enhanced by %s - adding anyways" % (suite, addsuite), "Warning: ")
+                            self.pkg.changes.setdefault("propdistribution", {})
+                            self.pkg.changes["propdistribution"][addsuite] = 1
+                            cansave = 1
+                        elif not target_version:
+                            # not targets_version is true when the package is NEW
+                            # we could just stick with the "...old version..." REJECT
+                            # for this, I think.
+                            self.reject("Won't propogate NEW packages.")
+                        elif apt_pkg.VersionCompare(new_version, add_version) < 0:
+                            # propogation would be redundant. no need to reject though.
+                            self.reject("ignoring versionconflict: %s: old version (%s) in %s <= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite), "Warning: ")
+                            cansave = 1
+                        elif apt_pkg.VersionCompare(new_version, add_version) > 0 and \
+                             apt_pkg.VersionCompare(add_version, target_version) >= 0:
+                            # propogate!!
+                            self.reject("Propogating upload to %s" % (addsuite), "Warning: ")
+                            self.pkg.changes.setdefault("propdistribution", {})
+                            self.pkg.changes["propdistribution"][addsuite] = 1
+                            cansave = 1
+
+                    if not cansave:
+                        self.reject("%s: old version (%s) in %s <= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
+
+    ################################################################################
+
+    def check_binary_against_db(self, file):
+        self.reject_message = ""
+        files = self.pkg.files
+
+        # Ensure version is sane
+        q = self.projectB.query("""
+SELECT b.version, su.suite_name FROM binaries b, bin_associations ba, suite su,
+                                     architecture a
+ WHERE b.package = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all')
+   AND ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id"""
+                                % (files[file]["package"],
+                                   files[file]["architecture"]))
+        self.cross_suite_version_check(q.getresult(), file,
+            files[file]["version"], sourceful=False)
+
+        # Check for any existing copies of the file
+        q = self.projectB.query("""
+SELECT b.id FROM binaries b, architecture a
+ WHERE b.package = '%s' AND b.version = '%s' AND a.arch_string = '%s'
+   AND a.id = b.architecture"""
+                                % (files[file]["package"],
+                                   files[file]["version"],
+                                   files[file]["architecture"]))
+        if q.getresult():
+            self.reject("%s: can not overwrite existing copy already in the archive." % (file))
+
+        return self.reject_message
+
+    ################################################################################
+
+    def check_source_against_db(self, file):
+        self.reject_message = ""
+        dsc = self.pkg.dsc
+
+        # Ensure version is sane
+        q = self.projectB.query("""
+SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
+ WHERE s.source = '%s' AND sa.source = s.id AND sa.suite = su.id""" % (dsc.get("source")))
+        self.cross_suite_version_check(q.getresult(), file, dsc.get("version"),
+            sourceful=True)
+
+        return self.reject_message
+
+    ################################################################################
+
+    # **WARNING**
+    # NB: this function can remove entries from the 'files' index [if
+    # the .orig.tar.gz is a duplicate of the one in the archive]; if
+    # you're iterating over 'files' and call this function as part of
+    # the loop, be sure to add a check to the top of the loop to
+    # ensure you haven't just tried to dereference the deleted entry.
+    # **WARNING**
+
+    def check_dsc_against_db(self, file):
+        self.reject_message = ""
+        files = self.pkg.files
+        dsc_files = self.pkg.dsc_files
+        legacy_source_untouchable = self.pkg.legacy_source_untouchable
+        self.pkg.orig_tar_gz = None
+
+        # Try and find all files mentioned in the .dsc.  This has
+        # to work harder to cope with the multiple possible
+        # locations of an .orig.tar.gz.
+        # The ordering on the select is needed to pick the newest orig
+        # when it exists in multiple places.
+        for dsc_file in dsc_files.keys():
+            found = None
+            if files.has_key(dsc_file):
+                actual_md5 = files[dsc_file]["md5sum"]
+                actual_size = int(files[dsc_file]["size"])
+                found = "%s in incoming" % (dsc_file)
+                # Check the file does not already exist in the archive
+                q = self.projectB.query("SELECT f.size, f.md5sum, l.path, f.filename FROM files f, location l WHERE f.filename LIKE '%%%s%%' AND l.id = f.location ORDER BY f.id DESC" % (dsc_file))
+                ql = q.getresult()
+                # Strip out anything that isn't '%s' or '/%s$'
+                for i in ql:
+                    if i[3] != dsc_file and i[3][-(len(dsc_file)+1):] != '/'+dsc_file:
+                        ql.remove(i)
+
+                # "[dak] has not broken them.  [dak] has fixed a
+                # brokenness.  Your crappy hack exploited a bug in
+                # the old dinstall.
+                #
+                # "(Come on!  I thought it was always obvious that
+                # one just doesn't release different files with
+                # the same name and version.)"
+                #                        -- ajk@ on d-devel@l.d.o
+
+                if ql:
+                    # Ignore exact matches for .orig.tar.gz
+                    match = 0
+                    if dsc_file.endswith(".orig.tar.gz"):
+                        for i in ql:
+                            if files.has_key(dsc_file) and \
+                               int(files[dsc_file]["size"]) == int(i[0]) and \
+                               files[dsc_file]["md5sum"] == i[1]:
+                                self.reject("ignoring %s, since it's already in the archive." % (dsc_file), "Warning: ")
+                                del files[dsc_file]
+                                self.pkg.orig_tar_gz = i[2] + i[3]
+                                match = 1
+
+                    if not match:
+                        self.reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file))
+            elif dsc_file.endswith(".orig.tar.gz"):
+                # Check in the pool
+                q = self.projectB.query("SELECT l.path, f.filename, l.type, f.id, l.id FROM files f, location l WHERE f.filename LIKE '%%%s%%' AND l.id = f.location" % (dsc_file))
+                ql = q.getresult()
+                # Strip out anything that isn't '%s' or '/%s$'
+                for i in ql:
+                    if i[1] != dsc_file and i[1][-(len(dsc_file)+1):] != '/'+dsc_file:
+                        ql.remove(i)
+
+                if ql:
+                    # Unfortunately, we may get more than one match here if,
+                    # for example, the package was in potato but had an -sa
+                    # upload in woody.  So we need to choose the right one.
+
+                    x = ql[0]; # default to something sane in case we don't match any or have only one
+
+                    if len(ql) > 1:
+                        for i in ql:
+                            old_file = i[0] + i[1]
+                            old_file_fh = utils.open_file(old_file)
+                            actual_md5 = apt_pkg.md5sum(old_file_fh)
+                            old_file_fh.close()
+                            actual_size = os.stat(old_file)[stat.ST_SIZE]
+                            if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
+                                x = i
+                            else:
+                                legacy_source_untouchable[i[3]] = ""
+
+                    old_file = x[0] + x[1]
+                    old_file_fh = utils.open_file(old_file)
+                    actual_md5 = apt_pkg.md5sum(old_file_fh)
+                    old_file_fh.close()
+                    actual_size = os.stat(old_file)[stat.ST_SIZE]
+                    found = old_file
+                    suite_type = x[2]
+                    dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
+                    # See install() in process-accepted...
+                    self.pkg.orig_tar_id = x[3]
+                    self.pkg.orig_tar_gz = old_file
+                    if suite_type == "legacy" or suite_type == "legacy-mixed":
+                        self.pkg.orig_tar_location = "legacy"
+                    else:
+                        self.pkg.orig_tar_location = x[4]
+                else:
+                    # Not there? Check the queue directories...
+
+                    in_unchecked = os.path.join(self.Cnf["Dir::Queue::Unchecked"],dsc_file)
+                    # See process_it() in 'dak process-unchecked' for explanation of this
+                    # in_unchecked check dropped by ajt 2007-08-28, how did that
+                    # ever make sense?
+                    if os.path.exists(in_unchecked) and False:
+                        return (self.reject_message, in_unchecked)
+                    else:
+                        for directory in [ "Accepted", "New", "Byhand", "ProposedUpdates", "OldProposedUpdates" ]:
+                            in_otherdir = os.path.join(self.Cnf["Dir::Queue::%s" % (directory)],dsc_file)
+                            if os.path.exists(in_otherdir):
+                                in_otherdir_fh = utils.open_file(in_otherdir)
+                                actual_md5 = apt_pkg.md5sum(in_otherdir_fh)
+                                in_otherdir_fh.close()
+                                actual_size = os.stat(in_otherdir)[stat.ST_SIZE]
+                                found = in_otherdir
+                                self.pkg.orig_tar_gz = in_otherdir
+
+                    if not found:
+                        self.reject("%s refers to %s, but I can't find it in the queue or in the pool." % (file, dsc_file))
+                        self.pkg.orig_tar_gz = -1
+                        continue
+            else:
+                self.reject("%s refers to %s, but I can't find it in the queue." % (file, dsc_file))
+                continue
+            if actual_md5 != dsc_files[dsc_file]["md5sum"]:
+                self.reject("md5sum for %s doesn't match %s." % (found, file))
+            if actual_size != int(dsc_files[dsc_file]["size"]):
+                self.reject("size for %s doesn't match %s." % (found, file))
+
+        return (self.reject_message, None)
+
+    def do_query(self, q):
+        sys.stderr.write("query: \"%s\" ... " % (q))
+        before = time.time()
+        r = self.projectB.query(q)
+        time_diff = time.time()-before
+        sys.stderr.write("took %.3f seconds.\n" % (time_diff))
+        return r
diff --git a/daklib/utils.py b/daklib/utils.py
new file mode 100755 (executable)
index 0000000..fc1465d
--- /dev/null
@@ -0,0 +1,1389 @@
+#!/usr/bin/env python
+# vim:set et ts=4 sw=4:
+
+# Utility functions
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+
+################################################################################
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
+       sys, tempfile, traceback, stat
+import apt_pkg
+import database
+from dak_exceptions import *
+
+################################################################################
+
+re_comments = re.compile(r"\#.*")
+re_no_epoch = re.compile(r"^\d+\:")
+re_no_revision = re.compile(r"-[^-]+$")
+re_arch_from_filename = re.compile(r"/binary-[^/]+/")
+re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
+re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
+re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
+
+re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
+re_multi_line_field = re.compile(r"^\s(.*)")
+re_taint_free = re.compile(r"^[-+~/\.\w]+$")
+
+re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
+re_gpg_uid = re.compile('^uid.*<([^>]*)>')
+
+re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
+re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
+
+re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
+
+default_config = "/etc/dak/dak.conf"
+default_apt_config = "/etc/dak/apt.conf"
+
+alias_cache = None
+key_uid_email_cache = {}
+
+# (hashname, function, earliest_changes_version)
+known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
+                ("sha256", apt_pkg.sha256sum, (1, 8))]
+
+################################################################################
+
+def open_file(filename, mode='r'):
+    try:
+        f = open(filename, mode)
+    except IOError:
+        raise CantOpenError, filename
+    return f
+
+################################################################################
+
+def our_raw_input(prompt=""):
+    if prompt:
+        sys.stdout.write(prompt)
+    sys.stdout.flush()
+    try:
+        ret = raw_input()
+        return ret
+    except EOFError:
+        sys.stderr.write("\nUser interrupt (^D).\n")
+        raise SystemExit
+
+################################################################################
+
+def extract_component_from_section(section):
+    component = ""
+
+    if section.find('/') != -1:
+        component = section.split('/')[0]
+
+    # Expand default component
+    if component == "":
+        if Cnf.has_key("Component::%s" % section):
+            component = section
+        else:
+            component = "main"
+
+    return (section, component)
+
+################################################################################
+
+def parse_deb822(contents, signing_rules=0):
+    error = ""
+    changes = {}
+
+    # Split the lines in the input, keeping the linebreaks.
+    lines = contents.splitlines(True)
+
+    if len(lines) == 0:
+        raise ParseChangesError, "[Empty changes file]"
+
+    # Reindex by line number so we can easily verify the format of
+    # .dsc files...
+    index = 0
+    indexed_lines = {}
+    for line in lines:
+        index += 1
+        indexed_lines[index] = line[:-1]
+
+    inside_signature = 0
+
+    num_of_lines = len(indexed_lines.keys())
+    index = 0
+    first = -1
+    while index < num_of_lines:
+        index += 1
+        line = indexed_lines[index]
+        if line == "":
+            if signing_rules == 1:
+                index += 1
+                if index > num_of_lines:
+                    raise InvalidDscError, index
+                line = indexed_lines[index]
+                if not line.startswith("-----BEGIN PGP SIGNATURE"):
+                    raise InvalidDscError, index
+                inside_signature = 0
+                break
+            else:
+                continue
+        if line.startswith("-----BEGIN PGP SIGNATURE"):
+            break
+        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
+            inside_signature = 1
+            if signing_rules == 1:
+                while index < num_of_lines and line != "":
+                    index += 1
+                    line = indexed_lines[index]
+            continue
+        # If we're not inside the signed data, don't process anything
+        if signing_rules >= 0 and not inside_signature:
+            continue
+        slf = re_single_line_field.match(line)
+        if slf:
+            field = slf.groups()[0].lower()
+            changes[field] = slf.groups()[1]
+            first = 1
+            continue
+        if line == " .":
+            changes[field] += '\n'
+            continue
+        mlf = re_multi_line_field.match(line)
+        if mlf:
+            if first == -1:
+                raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
+            if first == 1 and changes[field] != "":
+                changes[field] += '\n'
+            first = 0
+            changes[field] += mlf.groups()[0] + '\n'
+            continue
+        error += line
+
+    if signing_rules == 1 and inside_signature:
+        raise InvalidDscError, index
+
+    changes["filecontents"] = "".join(lines)
+
+    if changes.has_key("source"):
+        # Strip the source version in brackets from the source field,
+        # put it in the "source-version" field instead.
+        srcver = re_srchasver.search(changes["source"])
+        if srcver:
+            changes["source"] = srcver.group(1)
+            changes["source-version"] = srcver.group(2)
+
+    if error:
+        raise ParseChangesError, error
+
+    return changes
+
+################################################################################
+
+def parse_changes(filename, signing_rules=0):
+    """Parses a changes file and returns a dictionary where each field is a
+key.  The mandatory first argument is the filename of the .changes
+file.
+
+signing_rules is an optional argument:
+
+ o If signing_rules == -1, no signature is required.
+ o If signing_rules == 0 (the default), a signature is required.
+ o If signing_rules == 1, it turns on the same strict format checking
+   as dpkg-source.
+
+The rules for (signing_rules == 1)-mode are:
+
+  o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
+    followed by any PGP header data and must end with a blank line.
+
+  o The data section must end with a blank line and must be followed by
+    "-----BEGIN PGP SIGNATURE-----".
+"""
+
+    changes_in = open_file(filename)
+    content = changes_in.read()
+    changes_in.close()
+    return parse_deb822(content, signing_rules)
+
+################################################################################
+
+def hash_key(hashname):
+    return '%ssum' % hashname
+
+################################################################################
+
+def create_hash(where, files, hashname, hashfunc):
+    """create_hash extends the passed files dict with the given hash by
+    iterating over all files on disk and passing them to the hashing
+    function given."""
+
+    rejmsg = []
+    for f in files.keys():
+        try:
+            file_handle = open_file(f)
+        except CantOpenError:
+            rejmsg.append("Could not open file %s for checksumming" % (f))
+
+        files[f][hash_key(hashname)] = hashfunc(file_handle)
+
+        file_handle.close()
+    return rejmsg
+
+################################################################################
+
+def check_hash(where, files, hashname, hashfunc):
+    """check_hash checks the given hash in the files dict against the actual
+    files on disk.  The hash values need to be present consistently in
+    all file entries.  It does not modify its input in any way."""
+
+    rejmsg = []
+    for f in files.keys():
+        file_handle = None
+        try:
+            try:
+                file_handle = open_file(f)
+    
+                # Check for the hash entry, to not trigger a KeyError.
+                if not files[f].has_key(hash_key(hashname)):
+                    rejmsg.append("%s: misses %s checksum in %s" % (f, hashname,
+                        where))
+                    continue
+    
+                # Actually check the hash for correctness.
+                if hashfunc(file_handle) != files[f][hash_key(hashname)]:
+                    rejmsg.append("%s: %s check failed in %s" % (f, hashname,
+                        where))
+            except CantOpenError:
+                # TODO: This happens when the file is in the pool.
+                # warn("Cannot open file %s" % f)
+                continue
+        finally:
+            if file_handle:
+                file_handle.close()
+    return rejmsg
+
+################################################################################
+
+def check_size(where, files):
+    """check_size checks the file sizes in the passed files dict against the
+    files on disk."""
+
+    rejmsg = []
+    for f in files.keys():
+        try:
+            entry = os.stat(f)
+        except OSError, exc:
+            if exc.errno == 2:
+                # TODO: This happens when the file is in the pool.
+                continue
+            raise
+
+        actual_size = entry[stat.ST_SIZE]
+        size = int(files[f]["size"])
+        if size != actual_size:
+            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
+                   % (f, actual_size, size, where))
+    return rejmsg
+
+################################################################################
+
+def check_hash_fields(what, manifest):
+    """check_hash_fields ensures that there are no checksum fields in the
+    given dict that we do not know about."""
+
+    rejmsg = []
+    hashes = map(lambda x: x[0], known_hashes)
+    for field in manifest:
+        if field.startswith("checksums-"):
+            hashname = field.split("-",1)[1]
+            if hashname not in hashes:
+                rejmsg.append("Unsupported checksum field for %s "\
+                    "in %s" % (hashname, what))
+    return rejmsg
+
+################################################################################
+
+def _ensure_changes_hash(changes, format, version, files, hashname, hashfunc):
+    if format >= version:
+        # The version should contain the specified hash.
+        func = check_hash
+
+        # Import hashes from the changes
+        rejmsg = parse_checksums(".changes", files, changes, hashname)
+        if len(rejmsg) > 0:
+            return rejmsg
+    else:
+        # We need to calculate the hash because it can't possibly
+        # be in the file.
+        func = create_hash
+    return func(".changes", files, hashname, hashfunc)
+
+# We could add the orig which might be in the pool to the files dict to
+# access the checksums easily.
+
+def _ensure_dsc_hash(dsc, dsc_files, hashname, hashfunc):
+    """ensure_dsc_hashes' task is to ensure that each and every *present* hash
+    in the dsc is correct, i.e. identical to the changes file and if necessary
+    the pool.  The latter task is delegated to check_hash."""
+
+    rejmsg = []
+    if not dsc.has_key('Checksums-%s' % (hashname,)):
+        return rejmsg
+    # Import hashes from the dsc
+    parse_checksums(".dsc", dsc_files, dsc, hashname)
+    # And check it...
+    rejmsg.extend(check_hash(".dsc", dsc_files, hashname, hashfunc))
+    return rejmsg
+
+################################################################################
+
+def ensure_hashes(changes, dsc, files, dsc_files):
+    rejmsg = []
+
+    # Make sure we recognise the format of the Files: field in the .changes
+    format = changes.get("format", "0.0").split(".", 1)
+    if len(format) == 2:
+        format = int(format[0]), int(format[1])
+    else:
+        format = int(float(format[0])), 0
+
+    # We need to deal with the original changes blob, as the fields we need
+    # might not be in the changes dict serialised into the .dak anymore.
+    orig_changes = parse_deb822(changes['filecontents'])
+
+    # Copy the checksums over to the current changes dict.  This will keep
+    # the existing modifications to it intact.
+    for field in orig_changes:
+        if field.startswith('checksums-'):
+            changes[field] = orig_changes[field]
+
+    # Check for unsupported hashes
+    rejmsg.extend(check_hash_fields(".changes", changes))
+    rejmsg.extend(check_hash_fields(".dsc", dsc))
+
+    # We have to calculate the hash if we have an earlier changes version than
+    # the hash appears in rather than require it exist in the changes file
+    for hashname, hashfunc, version in known_hashes:
+        rejmsg.extend(_ensure_changes_hash(changes, format, version, files,
+            hashname, hashfunc))
+        if "source" in changes["architecture"]:
+            rejmsg.extend(_ensure_dsc_hash(dsc, dsc_files, hashname,
+                hashfunc))
+
+    return rejmsg
+
+def parse_checksums(where, files, manifest, hashname):
+    rejmsg = []
+    field = 'checksums-%s' % hashname
+    if not field in manifest:
+        return rejmsg
+    input = manifest[field]
+    for line in input.split('\n'):
+        if not line:
+            break
+        hash, size, file = line.strip().split(' ')
+        if not files.has_key(file):
+        # TODO: check for the file's entry in the original files dict, not
+        # the one modified by (auto)byhand and other weird stuff
+        #    rejmsg.append("%s: not present in files but in checksums-%s in %s" %
+        #        (file, hashname, where))
+            continue
+        if not files[file]["size"] == size:
+            rejmsg.append("%s: size differs for files and checksums-%s entry "\
+                "in %s" % (file, hashname, where))
+            continue
+        files[file][hash_key(hashname)] = hash
+    for f in files.keys():
+        if not files[f].has_key(hash_key(hashname)):
+            rejmsg.append("%s: no entry in checksums-%s in %s" % (file,
+                hashname, where))
+    return rejmsg
+
+################################################################################
+
+# Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
+
+def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
+    files = {}
+
+    # Make sure we have a Files: field to parse...
+    if not changes.has_key(field):
+        raise NoFilesFieldError
+
+    # Make sure we recognise the format of the Files: field
+    format = re_verwithext.search(changes.get("format", "0.0"))
+    if not format:
+        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
+
+    format = format.groups()
+    if format[1] == None:
+        format = int(float(format[0])), 0, format[2]
+    else:
+        format = int(format[0]), int(format[1]), format[2]
+    if format[2] == None:
+        format = format[:2]
+
+    if is_a_dsc:
+        # format = (1,0) are the only formats we currently accept,
+        # format = (0,0) are missing format headers of which we still
+        # have some in the archive.
+        if format != (1,0) and format != (0,0):
+            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
+    else:
+        if (format < (1,5) or format > (1,8)):
+            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
+        if field != "files" and format < (1,8):
+            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
+
+    includes_section = (not is_a_dsc) and field == "files"
+
+    # Parse each entry/line:
+    for i in changes[field].split('\n'):
+        if not i:
+            break
+        s = i.split()
+        section = priority = ""
+        try:
+            if includes_section:
+                (md5, size, section, priority, name) = s
+            else:
+                (md5, size, name) = s
+        except ValueError:
+            raise ParseChangesError, i
+
+        if section == "":
+            section = "-"
+        if priority == "":
+            priority = "-"
+
+        (section, component) = extract_component_from_section(section)
+
+        files[name] = Dict(size=size, section=section,
+                           priority=priority, component=component)
+        files[name][hashname] = md5
+
+    return files
+
+################################################################################
+
+def force_to_utf8(s):
+    """Forces a string to UTF-8.  If the string isn't already UTF-8,
+it's assumed to be ISO-8859-1."""
+    try:
+        unicode(s, 'utf-8')
+        return s
+    except UnicodeError:
+        latin1_s = unicode(s,'iso8859-1')
+        return latin1_s.encode('utf-8')
+
+def rfc2047_encode(s):
+    """Encodes a (header) string per RFC2047 if necessary.  If the
+string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
+    try:
+        codecs.lookup('ascii')[1](s)
+        return s
+    except UnicodeError:
+        pass
+    try:
+        codecs.lookup('utf-8')[1](s)
+        h = email.Header.Header(s, 'utf-8', 998)
+        return str(h)
+    except UnicodeError:
+        h = email.Header.Header(s, 'iso-8859-1', 998)
+        return str(h)
+
+################################################################################
+
+# <Culus> 'The standard sucks, but my tool is supposed to interoperate
+#          with it. I know - I'll fix the suckage and make things
+#          incompatible!'
+
+def fix_maintainer (maintainer):
+    """Parses a Maintainer or Changed-By field and returns:
+  (1) an RFC822 compatible version,
+  (2) an RFC2047 compatible version,
+  (3) the name
+  (4) the email
+
+The name is forced to UTF-8 for both (1) and (3).  If the name field
+contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
+switched to 'email (name)' format."""
+    maintainer = maintainer.strip()
+    if not maintainer:
+        return ('', '', '', '')
+
+    if maintainer.find("<") == -1:
+        email = maintainer
+        name = ""
+    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
+        email = maintainer[1:-1]
+        name = ""
+    else:
+        m = re_parse_maintainer.match(maintainer)
+        if not m:
+            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
+        name = m.group(1)
+        email = m.group(2)
+
+    # Get an RFC2047 compliant version of the name
+    rfc2047_name = rfc2047_encode(name)
+
+    # Force the name to be UTF-8
+    name = force_to_utf8(name)
+
+    if name.find(',') != -1 or name.find('.') != -1:
+        rfc822_maint = "%s (%s)" % (email, name)
+        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
+    else:
+        rfc822_maint = "%s <%s>" % (name, email)
+        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
+
+    if email.find("@") == -1 and email.find("buildd_") != 0:
+        raise ParseMaintError, "No @ found in email address part."
+
+    return (rfc822_maint, rfc2047_maint, name, email)
+
+################################################################################
+
+# sendmail wrapper, takes _either_ a message string or a file as arguments
+def send_mail (message, filename=""):
+        # If we've been passed a string dump it into a temporary file
+    if message:
+        filename = tempfile.mktemp()
+        fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
+        os.write (fd, message)
+        os.close (fd)
+
+    # Invoke sendmail
+    (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
+    if (result != 0):
+        raise SendmailFailedError, output
+
+    # Clean up any temporary files
+    if message:
+        os.unlink (filename)
+
+################################################################################
+
+def poolify (source, component):
+    if component:
+        component += '/'
+    if source[:3] == "lib":
+        return component + source[:4] + '/' + source + '/'
+    else:
+        return component + source[:1] + '/' + source + '/'
+
+################################################################################
+
+def move (src, dest, overwrite = 0, perms = 0664):
+    if os.path.exists(dest) and os.path.isdir(dest):
+        dest_dir = dest
+    else:
+        dest_dir = os.path.dirname(dest)
+    if not os.path.exists(dest_dir):
+        umask = os.umask(00000)
+        os.makedirs(dest_dir, 02775)
+        os.umask(umask)
+    #print "Moving %s to %s..." % (src, dest)
+    if os.path.exists(dest) and os.path.isdir(dest):
+        dest += '/' + os.path.basename(src)
+    # Don't overwrite unless forced to
+    if os.path.exists(dest):
+        if not overwrite:
+            fubar("Can't move %s to %s - file already exists." % (src, dest))
+        else:
+            if not os.access(dest, os.W_OK):
+                fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
+    shutil.copy2(src, dest)
+    os.chmod(dest, perms)
+    os.unlink(src)
+
+def copy (src, dest, overwrite = 0, perms = 0664):
+    if os.path.exists(dest) and os.path.isdir(dest):
+        dest_dir = dest
+    else:
+        dest_dir = os.path.dirname(dest)
+    if not os.path.exists(dest_dir):
+        umask = os.umask(00000)
+        os.makedirs(dest_dir, 02775)
+        os.umask(umask)
+    #print "Copying %s to %s..." % (src, dest)
+    if os.path.exists(dest) and os.path.isdir(dest):
+        dest += '/' + os.path.basename(src)
+    # Don't overwrite unless forced to
+    if os.path.exists(dest):
+        if not overwrite:
+            raise FileExistsError
+        else:
+            if not os.access(dest, os.W_OK):
+                raise CantOverwriteError
+    shutil.copy2(src, dest)
+    os.chmod(dest, perms)
+
+################################################################################
+
+def where_am_i ():
+    res = socket.gethostbyaddr(socket.gethostname())
+    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
+    if database_hostname:
+        return database_hostname
+    else:
+        return res[0]
+
+def which_conf_file ():
+    res = socket.gethostbyaddr(socket.gethostname())
+    if Cnf.get("Config::" + res[0] + "::DakConfig"):
+        return Cnf["Config::" + res[0] + "::DakConfig"]
+    else:
+        return default_config
+
+def which_apt_conf_file ():
+    res = socket.gethostbyaddr(socket.gethostname())
+    if Cnf.get("Config::" + res[0] + "::AptConfig"):
+        return Cnf["Config::" + res[0] + "::AptConfig"]
+    else:
+        return default_apt_config
+
+def which_alias_file():
+    hostname = socket.gethostbyaddr(socket.gethostname())[0]
+    aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
+    if os.path.exists(aliasfn):
+        return aliasfn
+    else:
+        return None
+
+################################################################################
+
+# Escape characters which have meaning to SQL's regex comparison operator ('~')
+# (woefully incomplete)
+
+def regex_safe (s):
+    s = s.replace('+', '\\\\+')
+    s = s.replace('.', '\\\\.')
+    return s
+
+################################################################################
+
+# Perform a substition of template
+def TemplateSubst(map, filename):
+    file = open_file(filename)
+    template = file.read()
+    for x in map.keys():
+        template = template.replace(x,map[x])
+    file.close()
+    return template
+
+################################################################################
+
+def fubar(msg, exit_code=1):
+    sys.stderr.write("E: %s\n" % (msg))
+    sys.exit(exit_code)
+
+def warn(msg):
+    sys.stderr.write("W: %s\n" % (msg))
+
+################################################################################
+
+# Returns the user name with a laughable attempt at rfc822 conformancy
+# (read: removing stray periods).
+def whoami ():
+    return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
+
+################################################################################
+
+def size_type (c):
+    t  = " B"
+    if c > 10240:
+        c = c / 1024
+        t = " KB"
+    if c > 10240:
+        c = c / 1024
+        t = " MB"
+    return ("%d%s" % (c, t))
+
+################################################################################
+
+def cc_fix_changes (changes):
+    o = changes.get("architecture", "")
+    if o:
+        del changes["architecture"]
+    changes["architecture"] = {}
+    for j in o.split():
+        changes["architecture"][j] = 1
+
+# Sort by source name, source version, 'have source', and then by filename
+def changes_compare (a, b):
+    try:
+        a_changes = parse_changes(a)
+    except:
+        return -1
+
+    try:
+        b_changes = parse_changes(b)
+    except:
+        return 1
+
+    cc_fix_changes (a_changes)
+    cc_fix_changes (b_changes)
+
+    # Sort by source name
+    a_source = a_changes.get("source")
+    b_source = b_changes.get("source")
+    q = cmp (a_source, b_source)
+    if q:
+        return q
+
+    # Sort by source version
+    a_version = a_changes.get("version", "0")
+    b_version = b_changes.get("version", "0")
+    q = apt_pkg.VersionCompare(a_version, b_version)
+    if q:
+        return q
+
+    # Sort by 'have source'
+    a_has_source = a_changes["architecture"].get("source")
+    b_has_source = b_changes["architecture"].get("source")
+    if a_has_source and not b_has_source:
+        return -1
+    elif b_has_source and not a_has_source:
+        return 1
+
+    # Fall back to sort by filename
+    return cmp(a, b)
+
+################################################################################
+
+def find_next_free (dest, too_many=100):
+    extra = 0
+    orig_dest = dest
+    while os.path.exists(dest) and extra < too_many:
+        dest = orig_dest + '.' + repr(extra)
+        extra += 1
+    if extra >= too_many:
+        raise NoFreeFilenameError
+    return dest
+
+################################################################################
+
+def result_join (original, sep = '\t'):
+    list = []
+    for i in xrange(len(original)):
+        if original[i] == None:
+            list.append("")
+        else:
+            list.append(original[i])
+    return sep.join(list)
+
+################################################################################
+
+def prefix_multi_line_string(str, prefix, include_blank_lines=0):
+    out = ""
+    for line in str.split('\n'):
+        line = line.strip()
+        if line or include_blank_lines:
+            out += "%s%s\n" % (prefix, line)
+    # Strip trailing new line
+    if out:
+        out = out[:-1]
+    return out
+
+################################################################################
+
+def validate_changes_file_arg(filename, require_changes=1):
+    """'filename' is either a .changes or .dak file.  If 'filename' is a
+.dak file, it's changed to be the corresponding .changes file.  The
+function then checks if the .changes file a) exists and b) is
+readable and returns the .changes filename if so.  If there's a
+problem, the next action depends on the option 'require_changes'
+argument:
+
+ o If 'require_changes' == -1, errors are ignored and the .changes
+                               filename is returned.
+ o If 'require_changes' == 0, a warning is given and 'None' is returned.
+ o If 'require_changes' == 1, a fatal error is raised.
+"""
+    error = None
+
+    orig_filename = filename
+    if filename.endswith(".dak"):
+        filename = filename[:-4]+".changes"
+
+    if not filename.endswith(".changes"):
+        error = "invalid file type; not a changes file"
+    else:
+        if not os.access(filename,os.R_OK):
+            if os.path.exists(filename):
+                error = "permission denied"
+            else:
+                error = "file not found"
+
+    if error:
+        if require_changes == 1:
+            fubar("%s: %s." % (orig_filename, error))
+        elif require_changes == 0:
+            warn("Skipping %s - %s" % (orig_filename, error))
+            return None
+        else: # We only care about the .dak file
+            return filename
+    else:
+        return filename
+
+################################################################################
+
+def real_arch(arch):
+    return (arch != "source" and arch != "all")
+
+################################################################################
+
+def join_with_commas_and(list):
+    if len(list) == 0: return "nothing"
+    if len(list) == 1: return list[0]
+    return ", ".join(list[:-1]) + " and " + list[-1]
+
+################################################################################
+
+def pp_deps (deps):
+    pp_deps = []
+    for atom in deps:
+        (pkg, version, constraint) = atom
+        if constraint:
+            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
+        else:
+            pp_dep = pkg
+        pp_deps.append(pp_dep)
+    return " |".join(pp_deps)
+
+################################################################################
+
+def get_conf():
+    return Cnf
+
+################################################################################
+
+# Handle -a, -c and -s arguments; returns them as SQL constraints
+def parse_args(Options):
+    # Process suite
+    if Options["Suite"]:
+        suite_ids_list = []
+        for suite in split_args(Options["Suite"]):
+            suite_id = database.get_suite_id(suite)
+            if suite_id == -1:
+                warn("suite '%s' not recognised." % (suite))
+            else:
+                suite_ids_list.append(suite_id)
+        if suite_ids_list:
+            con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
+        else:
+            fubar("No valid suite given.")
+    else:
+        con_suites = ""
+
+    # Process component
+    if Options["Component"]:
+        component_ids_list = []
+        for component in split_args(Options["Component"]):
+            component_id = database.get_component_id(component)
+            if component_id == -1:
+                warn("component '%s' not recognised." % (component))
+            else:
+                component_ids_list.append(component_id)
+        if component_ids_list:
+            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
+        else:
+            fubar("No valid component given.")
+    else:
+        con_components = ""
+
+    # Process architecture
+    con_architectures = ""
+    if Options["Architecture"]:
+        arch_ids_list = []
+        check_source = 0
+        for architecture in split_args(Options["Architecture"]):
+            if architecture == "source":
+                check_source = 1
+            else:
+                architecture_id = database.get_architecture_id(architecture)
+                if architecture_id == -1:
+                    warn("architecture '%s' not recognised." % (architecture))
+                else:
+                    arch_ids_list.append(architecture_id)
+        if arch_ids_list:
+            con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
+        else:
+            if not check_source:
+                fubar("No valid architecture given.")
+    else:
+        check_source = 1
+
+    return (con_suites, con_architectures, con_components, check_source)
+
+################################################################################
+
+# Inspired(tm) by Bryn Keller's print_exc_plus (See
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
+
+def print_exc():
+    tb = sys.exc_info()[2]
+    while tb.tb_next:
+        tb = tb.tb_next
+    stack = []
+    frame = tb.tb_frame
+    while frame:
+        stack.append(frame)
+        frame = frame.f_back
+    stack.reverse()
+    traceback.print_exc()
+    for frame in stack:
+        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
+                                             frame.f_code.co_filename,
+                                             frame.f_lineno)
+        for key, value in frame.f_locals.items():
+            print "\t%20s = " % key,
+            try:
+                print value
+            except:
+                print "<unable to print>"
+
+################################################################################
+
+def try_with_debug(function):
+    try:
+        function()
+    except SystemExit:
+        raise
+    except:
+        print_exc()
+
+################################################################################
+
+# Function for use in sorting lists of architectures.
+# Sorts normally except that 'source' dominates all others.
+
+def arch_compare_sw (a, b):
+    if a == "source" and b == "source":
+        return 0
+    elif a == "source":
+        return -1
+    elif b == "source":
+        return 1
+
+    return cmp (a, b)
+
+################################################################################
+
+# Split command line arguments which can be separated by either commas
+# or whitespace.  If dwim is set, it will complain about string ending
+# in comma since this usually means someone did 'dak ls -a i386, m68k
+# foo' or something and the inevitable confusion resulting from 'm68k'
+# being treated as an argument is undesirable.
+
+def split_args (s, dwim=1):
+    if s.find(",") == -1:
+        return s.split()
+    else:
+        if s[-1:] == "," and dwim:
+            fubar("split_args: found trailing comma, spurious space maybe?")
+        return s.split(",")
+
+################################################################################
+
+def Dict(**dict): return dict
+
+########################################
+
+# Our very own version of commands.getouputstatus(), hacked to support
+# gpgv's status fd.
+def gpgv_get_status_output(cmd, status_read, status_write):
+    cmd = ['/bin/sh', '-c', cmd]
+    p2cread, p2cwrite = os.pipe()
+    c2pread, c2pwrite = os.pipe()
+    errout, errin = os.pipe()
+    pid = os.fork()
+    if pid == 0:
+        # Child
+        os.close(0)
+        os.close(1)
+        os.dup(p2cread)
+        os.dup(c2pwrite)
+        os.close(2)
+        os.dup(errin)
+        for i in range(3, 256):
+            if i != status_write:
+                try:
+                    os.close(i)
+                except:
+                    pass
+        try:
+            os.execvp(cmd[0], cmd)
+        finally:
+            os._exit(1)
+
+    # Parent
+    os.close(p2cread)
+    os.dup2(c2pread, c2pwrite)
+    os.dup2(errout, errin)
+
+    output = status = ""
+    while 1:
+        i, o, e = select.select([c2pwrite, errin, status_read], [], [])
+        more_data = []
+        for fd in i:
+            r = os.read(fd, 8196)
+            if len(r) > 0:
+                more_data.append(fd)
+                if fd == c2pwrite or fd == errin:
+                    output += r
+                elif fd == status_read:
+                    status += r
+                else:
+                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
+        if not more_data:
+            pid, exit_status = os.waitpid(pid, 0)
+            try:
+                os.close(status_write)
+                os.close(status_read)
+                os.close(c2pread)
+                os.close(c2pwrite)
+                os.close(p2cwrite)
+                os.close(errin)
+                os.close(errout)
+            except:
+                pass
+            break
+
+    return output, status, exit_status
+
+################################################################################
+
+def process_gpgv_output(status):
+    # Process the status-fd output
+    keywords = {}
+    internal_error = ""
+    for line in status.split('\n'):
+        line = line.strip()
+        if line == "":
+            continue
+        split = line.split()
+        if len(split) < 2:
+            internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
+            continue
+        (gnupg, keyword) = split[:2]
+        if gnupg != "[GNUPG:]":
+            internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
+            continue
+        args = split[2:]
+        if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
+            internal_error += "found duplicate status token ('%s').\n" % (keyword)
+            continue
+        else:
+            keywords[keyword] = args
+
+    return (keywords, internal_error)
+
+################################################################################
+
+def retrieve_key (filename, keyserver=None, keyring=None):
+    """Retrieve the key that signed 'filename' from 'keyserver' and
+add it to 'keyring'.  Returns nothing on success, or an error message
+on error."""
+
+    # Defaults for keyserver and keyring
+    if not keyserver:
+        keyserver = Cnf["Dinstall::KeyServer"]
+    if not keyring:
+        keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
+
+    # Ensure the filename contains no shell meta-characters or other badness
+    if not re_taint_free.match(filename):
+        return "%s: tainted filename" % (filename)
+
+    # Invoke gpgv on the file
+    status_read, status_write = os.pipe();
+    cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
+    (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
+
+    # Process the status-fd output
+    (keywords, internal_error) = process_gpgv_output(status)
+    if internal_error:
+        return internal_error
+
+    if not keywords.has_key("NO_PUBKEY"):
+        return "didn't find expected NO_PUBKEY in gpgv status-fd output"
+
+    fingerprint = keywords["NO_PUBKEY"][0]
+    # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
+    # it'll try to create a lockfile in /dev.  A better solution might
+    # be a tempfile or something.
+    cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
+          % (Cnf["Dinstall::SigningKeyring"])
+    cmd += " --keyring %s --keyserver %s --recv-key %s" \
+           % (keyring, keyserver, fingerprint)
+    (result, output) = commands.getstatusoutput(cmd)
+    if (result != 0):
+        return "'%s' failed with exit code %s" % (cmd, result)
+
+    return ""
+
+################################################################################
+
+def gpg_keyring_args(keyrings=None):
+    if not keyrings:
+        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
+
+    return " ".join(["--keyring %s" % x for x in keyrings])
+
+################################################################################
+
+def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
+    """Check the signature of a file and return the fingerprint if the
+signature is valid or 'None' if it's not.  The first argument is the
+filename whose signature should be checked.  The second argument is a
+reject function and is called when an error is found.  The reject()
+function must allow for two arguments: the first is the error message,
+the second is an optional prefix string.  It's possible for reject()
+to be called more than once during an invocation of check_signature().
+The third argument is optional and is the name of the files the
+detached signature applies to.  The fourth argument is optional and is
+a *list* of keyrings to use.  'autofetch' can either be None, True or
+False.  If None, the default behaviour specified in the config will be
+used."""
+
+    # Ensure the filename contains no shell meta-characters or other badness
+    if not re_taint_free.match(sig_filename):
+        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
+        return None
+
+    if data_filename and not re_taint_free.match(data_filename):
+        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
+        return None
+
+    if not keyrings:
+        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
+
+    # Autofetch the signing key if that's enabled
+    if autofetch == None:
+        autofetch = Cnf.get("Dinstall::KeyAutoFetch")
+    if autofetch:
+        error_msg = retrieve_key(sig_filename)
+        if error_msg:
+            reject(error_msg)
+            return None
+
+    # Build the command line
+    status_read, status_write = os.pipe();
+    cmd = "gpgv --status-fd %s %s %s %s" % (
+        status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
+
+    # Invoke gpgv on the file
+    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
+
+    # Process the status-fd output
+    (keywords, internal_error) = process_gpgv_output(status)
+
+    # If we failed to parse the status-fd output, let's just whine and bail now
+    if internal_error:
+        reject("internal error while performing signature check on %s." % (sig_filename))
+        reject(internal_error, "")
+        reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
+        return None
+
+    bad = ""
+    # Now check for obviously bad things in the processed output
+    if keywords.has_key("KEYREVOKED"):
+        reject("The key used to sign %s has been revoked." % (sig_filename))
+        bad = 1
+    if keywords.has_key("BADSIG"):
+        reject("bad signature on %s." % (sig_filename))
+        bad = 1
+    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
+        reject("failed to check signature on %s." % (sig_filename))
+        bad = 1
+    if keywords.has_key("NO_PUBKEY"):
+        args = keywords["NO_PUBKEY"]
+        if len(args) >= 1:
+            key = args[0]
+        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
+        bad = 1
+    if keywords.has_key("BADARMOR"):
+        reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
+        bad = 1
+    if keywords.has_key("NODATA"):
+        reject("no signature found in %s." % (sig_filename))
+        bad = 1
+    if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
+        args = keywords["KEYEXPIRED"]
+        if len(args) >= 1:
+            key = args[0]
+        reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
+        bad = 1
+
+    if bad:
+        return None
+
+    # Next check gpgv exited with a zero return code
+    if exit_status:
+        reject("gpgv failed while checking %s." % (sig_filename))
+        if status.strip():
+            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
+        else:
+            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
+        return None
+
+    # Sanity check the good stuff we expect
+    if not keywords.has_key("VALIDSIG"):
+        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
+        bad = 1
+    else:
+        args = keywords["VALIDSIG"]
+        if len(args) < 1:
+            reject("internal error while checking signature on %s." % (sig_filename))
+            bad = 1
+        else:
+            fingerprint = args[0]
+    if not keywords.has_key("GOODSIG"):
+        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
+        bad = 1
+    if not keywords.has_key("SIG_ID"):
+        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
+        bad = 1
+
+    # Finally ensure there's not something we don't recognise
+    known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
+                          SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
+                          NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
+
+    for keyword in keywords.keys():
+        if not known_keywords.has_key(keyword):
+            reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
+            bad = 1
+
+    if bad:
+        return None
+    else:
+        return fingerprint
+
+################################################################################
+
+def gpg_get_key_addresses(fingerprint):
+    """retreive email addresses from gpg key uids for a given fingerprint"""
+    addresses = key_uid_email_cache.get(fingerprint)
+    if addresses != None:
+        return addresses
+    addresses = set()
+    cmd = "gpg --no-default-keyring %s --fingerprint %s" \
+                % (gpg_keyring_args(), fingerprint)
+    (result, output) = commands.getstatusoutput(cmd)
+    if result == 0:
+        for l in output.split('\n'):
+            m = re_gpg_uid.match(l)
+            if m:
+                addresses.add(m.group(1))
+    key_uid_email_cache[fingerprint] = addresses
+    return addresses
+
+################################################################################
+
+# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
+
+def wrap(paragraph, max_length, prefix=""):
+    line = ""
+    s = ""
+    have_started = 0
+    words = paragraph.split()
+
+    for word in words:
+        word_size = len(word)
+        if word_size > max_length:
+            if have_started:
+                s += line + '\n' + prefix
+            s += word + '\n' + prefix
+        else:
+            if have_started:
+                new_length = len(line) + word_size + 1
+                if new_length > max_length:
+                    s += line + '\n' + prefix
+                    line = word
+                else:
+                    line += ' ' + word
+            else:
+                line = word
+        have_started = 1
+
+    if have_started:
+        s += line
+
+    return s
+
+################################################################################
+
+# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
+# Returns fixed 'src'
+def clean_symlink (src, dest, root):
+    src = src.replace(root, '', 1)
+    dest = dest.replace(root, '', 1)
+    dest = os.path.dirname(dest)
+    new_src = '../' * len(dest.split('/'))
+    return new_src + src
+
+################################################################################
+
+def temp_filename(directory=None, dotprefix=None, perms=0700):
+    """Return a secure and unique filename by pre-creating it.
+If 'directory' is non-null, it will be the directory the file is pre-created in.
+If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
+
+    if directory:
+        old_tempdir = tempfile.tempdir
+        tempfile.tempdir = directory
+
+    filename = tempfile.mktemp()
+
+    if dotprefix:
+        filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
+    fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
+    os.close(fd)
+
+    if directory:
+        tempfile.tempdir = old_tempdir
+
+    return filename
+
+################################################################################
+
+# checks if the user part of the email is listed in the alias file
+
+def is_email_alias(email):
+    global alias_cache
+    if alias_cache == None:
+        aliasfn = which_alias_file()
+        alias_cache = set()
+        if aliasfn:
+            for l in open(aliasfn):
+                alias_cache.add(l.split(':')[0])
+    uid = email.split('@')[0]
+    return uid in alias_cache
+
+################################################################################
+
+apt_pkg.init()
+
+Cnf = apt_pkg.newConfiguration()
+apt_pkg.ReadConfigFileISC(Cnf,default_config)
+
+if which_conf_file() != default_config:
+    apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
+
+################################################################################
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..5213b35
--- /dev/null
@@ -0,0 +1,8 @@
+dak (0notforuse0-0) unstable; urgency=low
+
+  * Initial non-release.  [The packaging is nowhere near complete; don't
+    bother trying to use it unaltered.]
+
+ -- James Troup <james@nocrew.org>  Tue, 16 May 2006 21:55:42 -0500
+
+
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..a44e363
--- /dev/null
@@ -0,0 +1,14 @@
+Source: dak
+Section: misc
+Priority: extra
+Build-Depends: postgresql-dev, libapt-pkg-dev, sp, docbook, docbook-utils, python
+Maintainer: James Troup <james@nocrew.org>
+Standards-Version: 3.5.6.0
+
+Package: dak
+Architecture: any
+Depends: ${python:Depends}, python-pygresql, python2.1-email | python (>= 2.2), python-apt, apt-utils, gnupg (>= 1.0.6-1), ${shlibs:Depends}, dpkg-dev, python-syck (>= 0.61.2-1), libemail-send-perl
+Suggests: lintian, linda, less, binutils-multiarch, symlinks, postgresql (>= 7.1.0), dsync
+Description: Debian's archive maintenance scripts
+ This is a collection of archive maintenance scripts used by the
+ Debian project.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..151c697
--- /dev/null
@@ -0,0 +1,31 @@
+This is Debian GNU's prepackaged version of dak, Debian's archive
+maintenance scripts.
+
+This package was put together by me, James Troup <james@nocrew.org>,
+from my sources.
+
+Programs Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>.
+
+dak generate-releases Copyright (C) 2004, 2005, 2006  Anthony Towns <aj@azure.humbug.org.au>
+dak generate-index-diffs Copyright (C) 2004, 2005, 2006  Anthony Towns <aj@azure.humbug.org.au>, Andreas Barth <aba@not.so.argh.org>
+dak override, mirror-split Copyright (C) 2004  Daniel Silverstone <dsilvers@digital-scurf.org>
+
+dak check-overrides Copyright (C) 2005  Jeroen van Wolffelaar <jeroen@wolffelaar.nl>
+dak queue-report Copyright (C) 2005  Joerg Jaspert  <ganneff@debian.org>
+dak rm Copyright (C) 2004  Martin Michlmayr <tbm@cyrius.com>
+
+dak is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2, or (at your option) any later
+version.
+
+dak is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License with
+your Debian GNU system, in /usr/share/common-licenses/GPL, or with the
+Debian GNU dak source package as the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+Boston, MA 02111-1307, USA.
diff --git a/debian/postinst b/debian/postinst
new file mode 100644 (file)
index 0000000..683de83
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+      # Default (blank) files so that programs at least run --help and stuff
+      touch /etc/dak/dak.conf
+      touch /etc/dak/apt.conf
+fi
diff --git a/debian/python-dep b/debian/python-dep
new file mode 100644 (file)
index 0000000..27ca72f
--- /dev/null
@@ -0,0 +1,3 @@
+import sys;
+
+print "python:Depends=python (>= %s), python (<< %s)" % (sys.version[:3],float(sys.version[:3])+0.1)
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..2e7bbc8
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/make -f
+# debian/rules file - for dak (0.0)
+# Based on sample debian/rules file - for GNU Hello (1.3).
+# Copyright 1994,1995 by Ian Jackson.
+# Copyright 1998,1999,2000,2001,2002,2006 James Troup
+# I hereby give you perpetual unlimited permission to copy,
+# modify and relicense this file, provided that you do not remove
+# my name from the file itself.  (I assert my moral right of
+# paternity under the Copyright, Designs and Patents Act 1988.)
+# This file may have to be extensively modified
+
+PYTHONVER := `/usr/bin/python -c 'import sys;print sys.version[:3]'`
+
+install_dir=install -d -m 755
+install_file=install -m 644
+install_script=install -m 755
+install_binary=install -m 755 -s
+
+build: build-stamp
+build-stamp:
+       $(MAKE)
+       $(MAKE) -C docs
+       touch $@
+
+clean: checkroot
+       $(checkdir)
+       -rm -rf debian/tmp debian/*~ debian/files* debian/substvars build-stamp
+       $(MAKE) clean
+
+binary-indep: checkroot build
+       $(checkdir)
+       -rm -rf debian/tmp
+
+       $(install_dir) debian/tmp/DEBIAN/
+       $(install_script) debian/postinst debian/tmp/DEBIAN/
+
+       $(install_dir) debian/tmp/usr/lib/python/site-packages/dak/lib/
+
+       $(install_file) dak/*.py debian/tmp/usr/lib/python/site-packages/dak/
+       $(install_file) dak/lib/*.py debian/tmp/usr/lib/python/site-packages/dak/lib/
+
+
+       $(install_dir) debian/tmp/usr/bin/
+       $(install_script) dak/shell.py debian/tmp/usr/bin/dak
+
+       $(install_dir) -m 755 debian/tmp/usr/share/man/man1/
+       $(install_file) docs/manpages/*.1 debian/tmp/usr/share/man/man1/
+       gzip -9v debian/tmp/usr/share/man/man1/*
+
+       $(install_dir) -m 755 debian/tmp/etc/dak/
+
+       $(install_dir) debian/tmp/usr/share/doc/dak/
+       $(install_file) debian/changelog debian/tmp/usr/share/doc/dak/changelog.Debian
+       $(install_file) README NEWS THANKS TODO debian/tmp/usr/share/doc/dak/
+       $(install_file) docs/README* debian/tmp/usr/share/doc/dak/
+       $(install_file) ChangeLog debian/tmp/usr/share/doc/dak/changelog
+       gzip -9v debian/tmp/usr/share/doc/dak/*
+       $(install_file) debian/copyright debian/tmp/usr/share/doc/dak/
+
+       $(install_dir) debian/tmp/usr/share/doc/dak/examples/
+       $(install_file) examples/dak.conf debian/tmp/usr/share/doc/dak/examples/
+       # Hoho (err, rather: FIXME)
+       $(install_file) *.sql debian/tmp/usr/share/doc/dak/examples/
+       gzip -9v debian/tmp/usr/share/doc/dak/examples/*
+
+       dpkg-shlibdeps sql-aptvc.so
+       /usr/bin/python debian/python-dep >> debian/substvars
+       dpkg-gencontrol -isp
+       chown -R root.root debian/tmp
+       chmod -R go=rX debian/tmp
+       dpkg --build debian/tmp ..
+
+binary-arch:
+
+define checkdir
+       test -f dak/ls.py -a -f debian/rules
+endef
+
+# Below here is fairly generic really
+
+binary:        binary-indep binary-arch
+
+checkroot:
+       $(checkdir)
+       test root = "`whoami`"
+
+.PHONY: binary binary-arch binary-indep clean checkroot
diff --git a/docs/NEWS b/docs/NEWS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/docs/README.assumptions b/docs/README.assumptions
new file mode 100644 (file)
index 0000000..2c33055
--- /dev/null
@@ -0,0 +1,14 @@
+Assumptions
+-----------
+
+o Usernames do not contain ",". [dak import-users-from-passwd]
+o Package names and versions do not contain "_" [dak cruft-report]
+o Suites are case-independent in conf files, but forced lower case in use. [dak make-suite-file-list]
+o Components are case-sensitive. [dak make-suite-file-list]
+o There's always source of some sort
+
+o If you have a large archive, you have a lot of memory and don't mind
+  it being used. [dak make-suite-file-list[, dak import-archive]]
+
+[Very incomplete...]
+
diff --git a/docs/README.config b/docs/README.config
new file mode 100644 (file)
index 0000000..c0e831f
--- /dev/null
@@ -0,0 +1,466 @@
+Explanation of configuration files options for dak
+==================================================
+
+DB
+--
+
+Essential.  List of database details, e.g.
+
+| DB
+| {
+|   Name "projectb";
+|   Host ""; 
+|   Port -1;
+| };
+
+Name (required): The name of the PostgreSQL database which has been created
+for dak.
+
+Host (required): The name of the host on which the database is located.  If
+the database is local, Host should be blank.
+
+Port (required): The port of the database.  If the port is the default
+value (5432), this option should be set to -1.
+
+================================================================================
+
+Dir
+---
+
+Mandatory.  List of directory locations, e.g.
+
+| Dir
+| {
+|   Root "/org/ftp.debian.org/ftp/";
+|   Pool "/org/ftp.debian.org/ftp/pool/";
+|   Templates "/org/ftp.debian.org/dak/templates/";
+|   PoolRoot "pool/";
+|   Override "/org/ftp.debian.org/scripts/override/";
+|   Lists "/org/ftp.debian.org/database/dists/";
+|   Log "/org/ftp.debian.org/log/";
+|   Morgue "/org/ftp.debian.org/morgue/";
+|   MorgueReject "reject";
+|   QueueBuild "/org/incoming.debian.org/buildd/";
+|   UrgencyLog "/org/ftp.debian.org/testing/urgencies/";
+|   Queue
+|   {  
+|     Accepted "/org/ftp.debian.org/queue/accepted/";
+|     Byhand "/org/ftp.debian.org/queue/byhand/";
+|     Done "/org/ftp.debian.org/queue/done/";
+|     Holding "/org/ftp.debian.org/queue/holding/";
+|     New "/org/ftp.debian.org/queue/new/";
+|     Reject "/org/ftp.debian.org/queue/reject/";
+|     Unchecked "/org/ftp.debian.org/queue/unchecked/";
+|   };
+| };
+
+Root (required): Specifies the path of the root of the FTP archive.
+
+Pool (required): This variable specifies the path of the pool
+directory.  Debian packages will be placed in the pool by 'dak
+process-accepted' after they have been accepted by dak
+process-unchecked.
+
+Templates (required): dak sends various mails and uses templates from
+this directory.
+
+PoolRoot (required): This variable specifies the basename of the pool
+directory.
+
+Override (optional): This directory optionally stores override files (used
+by 'dak make-overrides').
+
+Lists (optional): This directory holds file lists used by apt-ftparchive to
+generate Packages and Sources files (used by 'dak make-suite-file-list').
+
+Log (required): Log files are put in this directory.
+
+Morgue (required): Removed files are moved there.  The morgue has various
+sub-directories, including (optionally) those defined by
+Clean-Queues::MorgueSubDir and Clean-Suites::MorgueSubDir.
+
+MorgueReject (required): if dak cannot move a rejected package to
+Dir::Queue::Reject, it will try to move it to the Dir::MorgueReject
+directory located under Dir::Morgue.
+
+QueueBuild (optional): This variable is only relevant if any suites
+are to be auto built, i.e. if Dinstall::QueueBuildSuites has any
+values.
+
+UrgencyLog (optional): If this directory is specified, 'dak
+process-accepted' will store the urgency value of each upload.  This
+is mainly used for britney (the testing script).
+
+Queue (required): This sub-tree defines important directories for the
+incoming queue.  The following variables have to be set: Accepted, Byhand
+Done, Holding, New, Reject, Unchecked.  An explanation of the function of
+these directores can be found in README.new-incoming.
+
+Queue::BTSVersionTrack (optional): this directory holds the DebBugs
+Version Tracking support files.
+
+================================================================================
+
+Suite
+-----
+
+Mandatory.  List of all suites, e.g. 
+
+| Suite
+| {
+|   Unstable
+|   {
+|      Components 
+|      {
+|        main;
+|      };
+|      Architectures 
+|      {
+|        source; 
+|        all;
+|        i386;
+|      };
+|      Announce "debian-devel-changes@lists.debian.org";
+|      Origin "Debian";
+|      Description "Debian Unstable - Not Released";
+|      CodeName "sid";
+|      OverrideCodeName "sid";
+|      Priority "5";
+|   };
+| };
+
+Announce (optional): controls where "Installed foo" mails are sent.
+
+CodeName, Origin and Description (optional): This settings are used by
+'dak generate-releases' and put in the Release files.
+
+OverrideCodeName (optional): used by 'dak make-overrides'.
+
+Priority (optional) determines which suite is used for the Maintainers file
+as generated by 'dak make-maintainers' (highest wins).
+
+CopyChanges (optional): if this variable is present it should be a path
+into the archive (i.e. "Dir::RootDir"); any upload targeted for a suite
+with this config option present will have the .changes file copied into
+that path.
+
+CopyDotDak (optional): if this is present it should be an absolute path; any
+upload targeted for a suite with this config option present will have the
+.dak file copied into that path.  This option is similar to CopyChanges
+and will most often be used with it; they're seperate because .changes
+files are mirrored and .dak files aren't, so the paths will usually be
+different.
+
+There are more optional variables, such as VersionChecks.  Please see
+dak.conf for examples.
+
+================================================================================
+
+SuiteMappings
+-------------
+
+Optional.  List of mappings for the Distribution file in a .changes file, e.g.:
+
+| SuiteMappings
+| {
+|    "map stable proposed-updates";
+|    "map frozen unstable";
+|    "map-unreleased stable unstable";
+|    "map-unreleased proposed-updates unstable";
+|    "ignore testing";
+| };
+
+There are three mapping types:
+
+(1) map <source> <dest>
+
+      Any target suite of '<source>' is unconditionally overriden to
+      '<dest>'.
+
+(2) map-unreleased <source> <dest>
+
+      Any upload targeted for suite '<source>' will be mapped to
+      '<dest>' iff it contains uploads for an architecture that is not
+      part of '<source>'.
+
+(3) ignore <suite>
+
+      Any target suite of '<suite>' is unconditionally removed from
+      the list of target suites.  NB: if the upload had only one
+      target suite this will lead to rejection.
+
+NB: ordering is not guaranteed.
+
+================================================================================
+
+Dinstall
+--------
+
+Mandatory.  List of dinstall options, e.g.:
+
+| Dinstall
+| {
+|    GPGKeyring {
+|       "/org/keyring.debian.org/keyrings/debian-keyring.gpg";
+|       "/org/keyring.debian.org/keyrings/debian-keyring.pgp";
+|    };
+|    SigningKeyring "/org/ftp.debian.org/s3kr1t/dot-gnupg/secring.gpg";
+|    SendmailCommand "/usr/sbin/sendmail -odq -oi -t";
+|    MyEmailAddress "Debian Installer <installer@ftp-master.debian.org>";
+|    MyAdminAddress "ftpmaster@debian.org";
+|    MyHost "debian.org";  // used for generating user@my_host addresses in e.g. manual_reject()
+|    MyDistribution "Debian";
+|    BugServer "bugs.debian.org";
+|    PackagesServer "packages.debian.org";
+|    TrackingServer "packages.qa.debian.org";
+|    LockFile "/org/ftp.debian.org/dak/lock";
+|    Bcc "archive@ftp-master.debian.org";
+|    FutureTimeTravelGrace 28800; // 8 hours
+|    PastCutoffYear "1984";
+|    BXANotify "false";
+|    QueueBuildSuites
+|    {
+|      unstable;
+|    };
+| };
+
+GPGKeyring (required): filenames of the PGP and GnuPG
+keyrings to be used by dak.
+
+SigningKeyring (optional): this is the private keyring used by 'dak
+generate-releases'.
+
+SendmailCommand (required): command to call the MTA.
+
+MyEmailAddress (required): this is used as the From: line for sending mails
+as a script/daemon.
+
+MyAdminAddress (required): used as a contact address in mails.
+
+MyDistribution (required): this variable is used in emails sent out by
+dak and others.  It should indicate the name of the distribution.
+
+BugServer (required): is used by 'dak process-unchecked' and 'dak rm'
+when closing bugs.
+
+PackagesServer (requried): used by 'dak rm' when carbon-copying a bug
+close mail to a package maintainer.
+
+TrackingServer (optional): used by 'dak process-unchecked' and 'dak
+rm' to send messages for the maintainer also to an alias for people
+tracking a specific source package.
+
+LockFile (required): contains the filename of the lockfile used by dinstall
+when in action mode (i.e. not using -n/--no-action).
+
+All sent mail is blind carbon copied to the email address in Bcc if it's
+not blank.
+
+FutureTimeTravelGrace (required): specifies how many seconds into the
+future timestamps are allowed to be inside a deb before being rejected.
+
+PastCutoffYear (required): specifies the cut-off year which is used when
+deciding whether or not to reject packages based on the file timestamp.
+
+BXANotify (optional): a boolean (default: no); if true (Debian-specific)
+BXA notification is sent.  The template for the BXA notification is located
+in Dir::Templates/process-new.bxa_notification and should be changed if this
+option is set.
+
+OverrideDisparityCheck (optional): a boolean (default: no); if true,
+dak process-unchecked compares an uploads section/priority with the overrides and whines
+at the maintainer if they differ.
+
+CloseBugs (optional): a boolean (default: no); if true the automated bug
+closing feature of dinstall is activated.
+
+QueueBuildSuites (optional): a list of suites which should be auto
+build.
+
+QueueBuild is a boolean; if true it activates support
+for auto-building from accepted.
+
+OverrideMaintainer (optional): be used to globally override the
+__MAINTAINER_TO__ and __MAINTAINER_FROM__ variables in template mails.
+Use with caution.
+
+SkipTime (required): an integer value which is the number of seconds that a
+file must be older than (via it's last modified timestamp) before dak process-unchecked
+will REJECT rather than SKIP the package.
+
+KeyAutoFetch (optional): boolean (default: false), which if set (and
+not overriden by explicit argument to check_signature()) will enable
+auto key retrieval.  Requires KeyServer and SigningKeyIds variables be
+set.  NB: you should only enable this variable on production systems
+if you have strict control of your upload queue.
+
+KeyServer (optional): keyserver used for key auto-retrieval
+(c.f. KeyAutoFetch).
+
+================================================================================
+
+Archive
+-------
+
+Mandatory.  List of all archives, e.g.
+
+| Archive
+| {
+|   ftp-master
+|   {
+|     OriginServer "ftp-master.debian.org";
+|     PrimaryMirror "ftp.debian.org";
+|     Description "Master Archive for the Debian project";
+|   };
+| };
+
+OriginServer and PrimaryMirror (required): used 'dak rm's bug closing mail
+templates.  The host name and it's OriginServer and Description are part of
+the SQL database in the 'archive' table.
+
+================================================================================
+
+Architectures
+-------------
+
+Mandatory.  List of all architectures, e.g.
+
+| Architectures
+| {
+|   source "Source";
+|   all           "Architecture Independent";
+|   i386   "Intel ia32";
+| };
+
+Both values go into the SQL database's 'architecture' table.
+The description is currently unused.
+
+================================================================================
+
+Component
+---------
+
+Mandatory.  List of all components, e.g.
+
+| Component
+| {
+|   main
+|   {
+|      Description "Main";
+|      MeetsDFSG "true";
+|   };
+| };
+
+All three values go into the SQL database's 'component' table.
+MeetsDFSG is currently unused.
+
+================================================================================
+
+Section
+-------
+
+Mandatory.  List of all valid sections, e.g.
+
+| Section
+| {
+|   base;
+| };
+
+The section goes into the 'section' table in SQL database.
+
+================================================================================
+
+Priority
+--------
+
+Mandatory.  List of all valid priorities, e.g.
+
+| Priority
+| {
+|   required 1;
+|   important 2;
+|   standard 3;
+|   optional 4;
+|   extra 5;
+|   source 0; // i.e. unused
+| };
+
+The value is the sorting key.  Both the section and it's sorting key
+go into the SQL database's 'priority' table.
+
+================================================================================
+
+OverrideType
+------------
+
+Mandatory.  List of al valid override types, e.g.
+
+| OverrideType
+| {
+|   deb;
+|   dsc;
+|   udeb;
+| };
+
+The type goes into the 'override_type' table in the SQL database.
+
+================================================================================
+
+Location
+--------
+
+Mandatory.  List all locations, e.g.
+
+| Location
+| {
+|   /org/ftp.debian.org/ftp/pool/
+|     {
+|       Archive "ftp-master";
+|       Type "pool";
+|      Suites
+|      {
+|        Stable;
+|        Unstable;
+|      };
+|     };
+| };
+
+There are three valid values for 'Type': 'legacy', 'legacy-mixed' and
+'pool'.  'legacy' and 'pool' are assumed to have sections for all
+components listed in the Components section 'legacy-mixed' are assumed
+to mix all components into one location.  The 'Archive' and 'Type'
+sections go into the SQL database's 'location' table.  'Suites' is a
+list of existent suites that should be used to populate the SQL
+database.
+
+Note that the archive value specified here must correspond to one defined
+in Archive.
+
+[Note: yes, this is horrible, it dates back to the original `import
+       the existent archive into the SQL Database' script ('dak import-archive') and
+       isn't otherwise used.  It should be revisted at some stage.]
+
+================================================================================
+
+Urgency
+-------
+
+Mandatory.
+
+| Urgency
+| {
+|   Default "low";
+|   Valid
+|   {
+|     low;
+|     medium;
+|     high;
+|     emergency;
+|     critical;
+|   };
+| };
+
+This defines the valid and default urgency of an upload.  If a package is
+uploaded with an urgency not listed here, it will be rejected.
+
+================================================================================
diff --git a/docs/README.first b/docs/README.first
new file mode 100644 (file)
index 0000000..9b7aa9c
--- /dev/null
@@ -0,0 +1,123 @@
+                               Notes
+                               =====
+
+o Please be careful: dak sends out lots of emails and if not
+  configured properly will happily send them to lots of people who
+  probably didn't want those emails.
+
+o Don't use the debian dak.conf, apt.conf, cron.* etc. as starting
+  points for your own configuration files, they're highly Debian
+  specific.  Start from scratch and refer to the security.debian.org
+  config files (-security) as they're a better example for a private
+  archive.
+
+                   What do all these scripts do?
+                   =============================
+
+Generic and generally useful
+----------------------------
+
+o To process queue/:
+
+  * dak process-unchecked - processes queue/unchecked
+  * dak process-accepted - move files from queue/accepted into the pool (and database)
+  * dak process-new - allows ftp administrator to processes queue/new and queue/byhand
+
+o To generate indices files:
+
+  * dak make-suite-file-list - generates file lists for apt-ftparchive
+                               and removes obsolete packages from
+                               suites 
+  * dak generate-releases - generates Release
+
+o To clean things up:
+
+  * dak clean-suites - to remove obsolete files from the pool
+  * dak clean-queues - to remove obsolete/stray files from the queue
+  * dak rm - to remove package(s) from suite(s)
+  * dak override - to change individual override entries
+
+o Information display:
+
+  * dak ls - shows information about package(s)
+  * dak queue-report - shows information about package(s) in queue/
+  * dak override - can show you individual override entries
+
+Generic and useful, but only for those with existing archives
+-------------------------------------------------------------
+
+o dak poolize - migrates packages from legacy locations to the pool
+o dak init-archive - initializes a projectb database from an exisiting archive
+
+Generic but not overly useful (in normal use)
+---------------------------------------------
+
+o dak dot-dak-decode - dumps info in .dak files
+o dak import-users-from-passwd - sync PostgreSQL users with system users
+o dak cruft-report - check for obsolete or duplicated packages
+o dak init-dirs - directory creation in the initial setup of an archive
+o dak check-archive - various sanity checks of the database and archive
+o dak control-overrides - manpiulates/list override entries
+o dak control-suite - removes/adds/lists package(s) from/to/for a suite
+o dak stats - produces various statistics
+o dak find-null-maintainers - checks for users with no packages in the archive
+
+Semi-generic
+------------
+
+To generate less-used indices files:
+
+o dak make-maintainers - generates Maintainers file used by, e.g. debbugs
+o dak make-overrides  - generates override.<foo> files
+
+Mostly Debian(.org) specific
+----------------------------
+
+o dak security-install - wrapper for Debian security team
+o dak clean-proposed-updates - removes obsolete .changes files from proposed-updates
+o dak check-proposed-updates - basic dependency checking for proposed-updates
+o dak reject-proposed-updates - manually reject packages from proposed-updates
+o dak import-ldap-fingerprints - syncs fingerprint and uid information with a debian.org LDAP DB
+
+Very Incomplete or otherwise not generally useful
+-------------------------------------------------
+
+o dak init-db - currently only initializes a DB from a dak.conf config file
+o dak compare-suites - looks for version descrepancies that shouldn't exist in many
+                      archives
+o dak check-overrides - override cruft checker that doesn't work well with New Incoming
+
+Scripts invoked by other scripts
+--------------------------------
+
+o dak examine-package - invoked by 'dak process-new' to "check" NEW packages
+o dak symlink-dists - invoked by 'dak poolize' to determine packages still in legacy locations
+
+                       How do I get started?
+                       =====================
+
+[Very incomplete - FIXME]
+
+o Write your own dak.conf and apt.conf files.  dak looks for those
+  config files in /etc/dak/.  /etc/dak/dak.conf can define
+  alternative configuration files with Config::host::DakConfig and
+  Config::host::AptConfig (where "host" is the fully qualified domain
+  name of your machine).
+o Create a PostgreSQL database on the host given in dak.conf's DB::Host
+  with the name specified in DB::Name.
+o Run 'dak init-dirs': this will create all directories which are specified in
+  dak.conf and apt.conf.
+o If you have an existing archive:
+   * Run 'dak init-archive'
+  otherwise:
+   * Create the table structure.  init_pool.sql contains all SQL statements
+     which are needed for this.  After changing all occurences of "projectb"
+     to the name of your database (as defined in DB::Name) you can run:
+         psql <DB::Name> < init_pool.sql
+   * Run 'dak init-db': it will populate your database with the values from
+     dak.conf and apt.conf.
+   * Run 'psql <DB::Name> < add_constraints.sql'.
+o Copy all templates from the "templates" directory to to the directory
+  specified in Dir::Templates, and adapt them to your distribution.
+o Create an 'ftpmaster' group in postgres.
+
diff --git a/docs/README.new-incoming b/docs/README.new-incoming
new file mode 100644 (file)
index 0000000..8ebd0e2
--- /dev/null
@@ -0,0 +1,123 @@
+[An updated version of the proposal sent to debian-devel-announce@l.d.o.  
+ Debian-specific, but useful as a general overview of New Incoming.]
+
+                     New Incoming System
+                    ===================
+
+This document outlines the new system for handling Incoming
+directories on ftp-master and non-US.
+
+The old system:
+---------------
+
+  o incoming was a world writable directory
+
+  o incoming was available to everyone through http://incoming.debian.org/
+
+  o incoming was processed once a day by dinstall
+
+  o uploads in incoming had to have been there > 24 hours before they
+    were REJECTed.  If they were processed before that and had
+    problems they were SKIPped (with no notification to the maintainer
+    and/or uploader).
+
+The new system:
+---------------
+
+  o There's 4 incoming directories:
+
+     @ "unchecked"  - where uploads from Queue Daemons and maintainers
+                     initially go.
+
+     @ "accepted"   - where accepted packages stay until the daily
+                      dinstall run.
+
+     @ "new"       - where NEW packages (and their dependents[1]) requiring
+                     human processing go after being automatically
+                     checked by dinstall.
+
+     @ "byhand"            - where BYHAND packages (and their dependents[1])
+                      requiring human intervention go after being
+                      automatically checked by dinstall.
+
+    In addition there's 3 support directories:
+
+     @ "reject"            - where rejected uploads go
+
+     @ "done"      - where the .changes files for packages that have been
+                     installed go.
+
+     @ "holding"    - a temporary working area for dinstall to hold
+                     packages while checking them.
+
+  o Packages in 'unchecked' are automatically checked every 15 minutes
+    and are either: REJECT, ACCEPT, NEW or BYHAND.
+
+  o Only 'unchecked' is locally world-writeable.  The others are all,
+    of course, locally world-readable but only 'accepted' and 'byhand'
+    are publicly visible on http://incoming.debian.org/
+
+  o 'accepted' and 'byhand' are made available to the auto-builders so
+     they can build out of them.
+
+  o 'accepted' is processed once a day as before.
+
+  o Maintainer/uploader & list notification and bug closures are
+    changed to be done for ACCEPTs, not INSTALLs. 
+    [Rationale: this reduces the load both on our list server and our
+     BTS server; it also gives people better notice of uploads to
+     avoid duplication of work especially, for example, in the case of
+     NMUs.]
+    [NB: see [3] for clarifications of when mails are sent.]
+
+Why:
+----
+
+  o Security (no more replaceable file races)
+  o Integrity (new http://i.d.o contains only signed (+installable) uploads[2])
+  o Needed for crypto-in-main integration
+  o Allows safe auto-building out of accepted
+  o Allows previously-prohibitively-expensive checks to be added to dinstall
+  o Much faster feedback on packages; no more 48 hour waits before
+    finding out your package has been REJECTed.
+
+What breaks:
+------------
+
+  o people who upload packages but then want to retract or replace the
+    upload.
+
+    * solution: mostly "Don't do that then"; i.e. test your uploads
+      properly.  Uploads can still be replaced, simply by uploading a
+      higher versioned replacement.  Total retraction is harder but
+      usually only relevant for NEW packages.
+
+================================================================================
+
+[1] For versions of dependents meaning: binaries compiled from the
+    source of BYHAND or NEW uploads.  Due to dak's fascist
+    source-must-exist checking, these binaries must be held back until
+    the BYHAND/NEW uploads are processed.
+
+[2] When this mail was initially written there was still at least one
+    upload queue which will accept unsigned uploads from any
+    source. [I've since discovered it's been deactivated, but not,
+    AFAIK because it allowed unsigned uploads.]
+
+[3]
+             --> reject
+            /
+           /
+unchecked  -----------------------------[*]------> accepted ---------------> pool
+           \               ^   ^
+            |             /   /
+            |-->   new  --   /
+            |       |[4]    /
+            |       V      /
+            |--> byhand --/
+
+[4] This is a corner case, included for completeness, ignore
+    it. [Boring details: NEW trumps BYHAND, so it's possible for a
+    upload with both BYHAND and NEW components to go from 'unchecked'
+    -> 'new' -> 'byhand' -> 'accepted']
+
diff --git a/docs/README.options b/docs/README.options
new file mode 100644 (file)
index 0000000..755b9a5
--- /dev/null
@@ -0,0 +1,13 @@
+Standard options
+----------------
+
+-a/--architectures
+-c/--component
+-h/--help 
+-n/--no-action
+-s/--suite
+-V/--version
+
+Exceptions:
+  dak process-unchecked - backwardly compatible options with dinstall
+  dak control-suite - ? hysterical raisins and testing compatibility
diff --git a/docs/README.quotes b/docs/README.quotes
new file mode 100644 (file)
index 0000000..3568ae7
--- /dev/null
@@ -0,0 +1,346 @@
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+| <mdz_> SirDibos: that sentence sounds like it wants to be a bug report when it grows up
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+| From: Andrew Morton <akpm@osdl.org>
+| Subject: Re: Linux 2.6.0-test1 Ext3 Ooops. Reboot needed.
+| To: Ricardo Galli <gallir@uib.es>
+| Cc: linux-kernel@vger.kernel.org
+| Date: Fri, 18 Jul 2003 14:27:20 -0700
+| 
+| Ricardo Galli <gallir@uib.es> wrote:
+| >
+| > "File alteration monitor", from Debian.
+| 
+| OK.
+| 
+| > $ apt-cache show fam
+| 
+| I was attacked by dselect as a small child and have since avoided debian. 
+| Is there a tarball anywhere?
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+| From: Bob Hilliard <hilliard@debian.org>
+| 
+|      In my experience, James has been very responsive, albeit not
+| verbose, to reasonable questions/requests that don't start out saying
+| "James is a bum - throw him out".
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+| <eigood> Kamion: are you too busy to look at my generic
+|          include/exclude stuff for the bts yet?
+| <Kamion> eigood: expect me to be busy for about the next week at this
+|          rate
+| <eigood> my %field_match = (
+| <eigood>     'subject' => \&contains_field_match,
+| <eigood>     'severity' => \&exact_field_match,
+| <eigood> that's how it works, basically
+| <eigood> I'm a big fan of callbacks
+| [...]
+| <eigood> Kamion: how do you feel about having
+|          per-bug/per-package/per-source notes support in the bts?
+| <Kamion> eigood: as I said five minutes ago, I really don't have time
+|          to think about it right now, sorry
+| <Kamion> here, maybe it would be clearer if I /part
+| <-- Kamion (~cjwatson@host81-129-36-235.in-addr.btopenworld.com) has left #debian-devel (too busy. no, really.)
+| <eigood> no need to be hostile
+| <Joy> eigood: he told you he's too busy and you kept bugging him. take
+|       a hint :)
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<mstone> bwahahaha. Dear "security@debian.org" Thank you for your
+         email about "[SECURITY] [DSA-403-1] userland can access Linux
+         kernel memory" ...I need to filter out spam... To send email to
+         vhs@flexdesign.com please put "ducks" anywhere on your subject
+         line. ...Thanks, Bob...
+<mstone> I'll be sure to do that...
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<drow> Hmm, that was a nice short bug report.
+<drow> to submit@: "strdup(NULL) segfaults" to -done@: "Yes, go away"
+[...]
+<Kamion> how did he pass T&S? sheer bloody-mindedness?
+[...]
+<drow> Good attention to detail?
+<drow> Masking of psychopathic tendencies?
+* drow shrugs
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<DanielS> the people love me
+<Joy> like pneumonia
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+test.c:5: `long long long' is too long for GCC
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+http://yro.slashdot.org/comments.pl?sid=91696&cid=7890274
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<Joy> argh.
+<Joy> i accidentally banned all mails to the bts that had 'ossi' in them
+<Joy> "possible" etc
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<http://www.livejournal.com/users/mjg59/2003/12/24/>
+
+Wednesday, December 24th, 2003
+3:34 pm        
+Dear PC World,
+
+1) The most common chipset used in Pentium-II machines is the Intel 440BX. It is also relatively common in slower P-IIIs, and is approximately identical to the 440MX (a one-chip version aimed at laptops).
+
+2) The 440BX has the interesting feature of only being able to address up to 128MBit density RAM. This is a relatively widely known issue.
+
+3) Simple maths suggests that if you have a 128MB DIMM with 4 chips on it, they are likely to be 256MBit parts.
+
+4) Marking said DIMMs as being suitable for Pentium-IIs is therefore really indescribably stupid, you wankwits. Please fuck off and die in a great big chemical fire before I get back there to beat you.
+
+Love,
+
+Matthew.
+
+PS,
+
+Die. No, really. 
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<http://www.livejournal.com/users/mjg59/2003/11/12/>
+
+Wednesday, November 12th, 2003
+2:43 am        
+It's true that you learn something new every day. Yesterday I discovered that playdough is electrically conductive. I also discovered that RAM becomes unhappy if all of its pins are joined together with electrically conductive material.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<http://www.livejournal.com/users/mjg59/2003/11/03/>
+
+Monday, November 3rd, 2003
+3:13 pm        
+Hint to people attempting to sell things online:
+
+DON'T PUT http://172.16.100.107/ IN YOUR URLS, YOU INCOMPETENT FUCKMONKEYS 
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+| priviledged positions? What privilege? The honour of working harder
+| than most people for absolutely no recognition?
+
+Manoj Srivastava <srivasta@debian.org> in <87lln8aqfm.fsf@glaurung.internal.golden-gryphon.com>
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<elmo_h> you could just r00t klecker through [...] and do it yourself
+<mdz> heh
+<mdz> I think there's a bit in the DMUP about that
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<Yoe> well, thing is, he doesn't seem to understand you usually don't
+      have the time to give everyone status updates when a fly moves a
+      leg
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+In Soviet Russia...
+
+The cops arrest YOU for not showing papers. Wait, I didn't have to
+reverse it this time, what's going on?
+
+http://slashdot.org/comments.pl?sid=97501&cid=8334726
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<infinity> <shrug>... Messaging IRCops isn't the end of the world,
+           unless its "/msg ircop I fucked your wife."
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+| From: Andrew Morton <akpm@osdl.org>
+| Subject: Re: [PATCH] Compile kernel with GCC-3.5 and without regparm
+| To: "Art Haas" <ahaas@airmail.net>
+| Cc: linux-kernel@vger.kernel.org
+| Date: Tue, 2 Mar 2004 16:59:28 -0800
+| X-Mailer: Sylpheed version 0.9.7 (GTK+ 1.2.10; i586-pc-linux-gnu)
+| 
+| "Art Haas" <ahaas@airmail.net> wrote:
+| >
+| > I tried to build the kernel with my CVS GCC-3.5 compiler today, and had
+| > all sorts of failures about prototypes not matching.
+| 
+| -mm is where the gcc-3.5 action is.  There seems to be a bit of an arms
+| race going on wherein the gcc developers are trying to break the kernel
+| build faster than I and others can fix it.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+(Note that the above is a gross oversimplification, and ignores issues
+including but not necessarily limited to subarchitectures, and quality
+of hardware coverage within certian architectures. It contains forward
+looking statements, and may cause cancer in lab animals.)
+
+Joey Hess in <20040317065216.GA29816@kitenet.net>
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<jdub> now there's a thought
+<jdub> DD trading cards
+<mdz> official joeyh action figure, with rapid-fire upload action
+<jdub> lamont with pump-action NMU flame-thrower!
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<aj> "attempt to de-scare ... may cause cancer"
+* aj thinks elmo needs to work on his de-scaring
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<mdz> the Thom Remote Management Unit
+<mdz> TRMU
+<thom> i cost 3 times the amount an IBM remote management card would. per use.
+<jdub> last time *i* checked, you were free
+<jdub> oh, different service
+<jdub> never mind
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+From: Stan Shebs <shebs@apple.com>
+Subject: Re: MS/CW-style inline assembly for GCC
+To: gcc@gcc.gnu.org
+Date: Mon, 03 May 2004 17:35:40 -0700
+
+Can you be more specific about the difficulties? The CW version didn't
+seem that hard (unless Apple mgmt is reading this, in which case it was
+fiendishly difficult :-) ), but I did impose some restrictions in edge
+[...]
+
+Stan
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+buildd@caballero:~/logs$ du -sh kernel-patch-powerpc-2.6.5_2.6.5-2_20040506-1033 
+54G     kernel-patch-powerpc-2.6.5_2.6.5-2_20040506-1033
+
+Next week on "When Good Buildds Go Bad"[...]
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<aj>  153 NP+ 11 James Troup     (7.9K) you know you want it.  err.
+* aj looks disturbed as he trolls through his saved mail
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+From: Andrew Morton <akpm@osdl.org>
+Subject: 2.6.6-mm5
+To: linux-kernel@vger.kernel.org
+Date: Sat, 22 May 2004 01:36:36 -0700
+X-Mailer: Sylpheed version 0.9.7 (GTK+ 1.2.10; i386-redhat-linux-gnu)
+
+[...]
+
+  Although this feature has been around for a while it is new code, and the
+  usual cautions apply.  If it munches all your files please tell Jens and
+  he'll type them in again for you.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+From: Randall Donald <ftpmaster@debian.org>
+Subject: foo_3.4-1_i386.changes REJECTED
+To: John Doe <jdoe@debian.org>
+Cc: Debian Installer <installer@ftp-master.debian.org>
+Date: Thu, 17 Jun 2004 23:17:48 -0400
+
+Hi,
+
+ Description: something to put here
+   Maybe I need a long description here!
+Yes, you do!
+
+--
+Randall Donald
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<Keybuk> I don't test, I upload to unstable and a milllllion users test it for me :)
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+This is based on an actual radio conversation between a U.S. Navy
+aircraft carrier, U.S.S. Abraham Lincoln, and Canadian authorities off
+the coast of Newfoundland in October, 1995. The radio conversation was
+released by the Chief of Naval Operations on 10/10/95 authorized by
+the Freedom of Information Act.
+
+Canadians: Please divert your course 15 degrees to the South to avoid
+          collision.
+
+Americans: Recommend you divert your course 15 degrees to the North to
+          avoid a collision.
+
+Canadians: Negative. You will have to divert your course 15 degrees to
+          the South to avoid a collision.
+
+Americans: This is the Captain of a US Navy ship. I say again, divert
+          YOUR course.
+
+Canadians: No, I say again, you divert YOUR course.
+
+Americans: THIS IS THE AIRCRAFT CARRIER USS LINCOLN, THE SECOND
+          LARGEST SHIP IN THE UNITED STATES' ATLANTIC FLEET. WE ARE
+          ACCOMPANIED BY THREE DESTROYERS, THREE CRUISERS AND
+          NUMEROUS SUPPORT VESSELS. I DEMAND THAT YOU CHANGE YOUR
+          COURSE 15 DEGREES NORTH--I SAY AGAIN, THAT'S ONE FIVE
+          DEGREES NORTH--OR COUNTER-MEASURES WILL BE UNDERTAKEN TO
+          ENSURE THE SAFETY OF THIS SHIP.
+
+Canadians: This is a lighthouse. Your call.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<neuro> i didn't slam today's dinstall too badly, did I?
+<elmo>   File "/org/ftp.debian.org/dak/process_accepted.py", line 608, in main
+<elmo>     sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
+<elmo> OverflowError: float too large to convert
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+"The Hurd's design is so secure that it makes firewalls immoral IMHO." -- Jeroen Dekkers
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<helix> I bought some foam soap for kids the other day and only
+        realized it had an elmo picture on it when I got home
+<helix> now I can't use it because I feel perverted
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<helix> hm hilarity did not ensue
+<helix> with my laundry
+<helix> I washed it with bubble bath :|
+<neuro> *snork*
+<helix> well, nothing bad happened; but my clothes also don't smell like 
+        cinnamon buns, like I'd hoped
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<bdale> I thought Jeroen's comment was interesting...
+<aj> What was that?
+<bdale> Well I didn't let him speak....
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+<helix> elmo: I can't believe people pay you to fix computers
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/docs/README.stable-point-release b/docs/README.stable-point-release
new file mode 100644 (file)
index 0000000..176b33e
--- /dev/null
@@ -0,0 +1,52 @@
+Rough Guide to doing Stable Point Releases in Debian
+----------------------------------------------------
+
+o run 'dak clean-proposed-updates' to get rid of obsolete .changes
+  from p-u ['dak clean-proposed-updates *.changes' from within p-u]
+o [also check for obsolete .changes caused by 'dak rm'-ing from p-u]
+o Update dak.conf (at least the section for Reject-Proposed-Updates,
+  before using 'dak reject-proposed-updates')
+o Install, reject and remove packages as directed by the SRM using
+  'dak process-accepted' (installs), 'dak reject-proposed-updates'
+  (rejects) and 'dak rm' (removals)
+
+  NB: removing packages are not logged to the stable ChangeLog; you
+      need to do that byhand.
+
+o If you installed a debian-installer upload; migrate the relevant
+  installer-*/$release directory from proposed-updates to stable.
+  (Including potentially removing older versions)
+
+o Decruft stable in coordination with SRMs
+
+o Do anything in proposed-updates/TODO 
+o Close any applicable stable bugs
+  (hint: http://bugs.debian.org/cgi-bin/pkgreport.cgi?pkg=ftp.debian.org&include=etch)
+o Update version number in README, README.html and dists/README
+o Update the 'Debian<n>.<n>r<n>' symlink in dists/
+o Clean up dists/stable/ChangeLog (add header, basically)
+o Update version fields in dak.conf
+o Update fields in suite table in postgresql (see below)
+
+o Run 'dak make-suite-file-list --force -s stable'
+o Run apt-ftparchive generate apt.conf.stable
+o Run 'dak generate-releases --force-touch --apt-conf apt.conf.stable stable'
+
+[Yes, this sucks and more of it should be automated. c.f. ~ajt/pointupdate]
+
+#######################################################
+
+update suite set version = '4.0r3' where suite_name = 'stable';
+update suite set description = 'Debian 4.0r3 Released 16th February 2008' where suite_name = 'stable';
+
+Rough Guide to doing Old-Stable Point Releases in Debian
+--------------------------------------------------------
+
+Pretty much as above, except that process-accepted doesn't know about
+oldstable, so you have to do some surgery on it first to make it
+support that.  Probably want to disable cron.daily whilst doing so.
+Also watch out for the installing_to_stable detection which doesn't
+work well with the current layout of oldstable-proposed-updates (as a
+symlink to $distro-proposed-updates).  clean-proposed-updates,
+cruft-report and most everything else support a -s/--suite so they
+sould be fine to use.
diff --git a/docs/THANKS b/docs/THANKS
new file mode 100644 (file)
index 0000000..99424b8
--- /dev/null
@@ -0,0 +1,36 @@
+The dak software is based in large part on 'dinstall' by Guy Maor.
+The original 'dak process-unchecked' script was pretty much a line by
+line reimplementation of the perl 'dinstall' in python.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+[Alphabetical Order]
+
+Adam Heath                    <doogie@debian.org>
+Andreas Barth                 <aba@not.so.argh.org>
+Anthony Towns                 <ajt@debian.org>
+Antti-Juhani Kaijanaho        <ajk@debian.org>
+Ben Collins                   <bcollins@debian.org>
+Brendan O'Dea                 <bod@debian.org>
+Daniel Jacobwitz              <dan@debian.org>
+Daniel Silverstone            <dsilvers@debian.org>
+Drake Diedrich                <dld@debian.org>
+Guy Maor                      <maor@debian.org>
+Jason Gunthorpe                       <jgg@debian.org>
+Jeroen van Wolffelaar         <jeroen@wolffelaar.nl>
+Joey Hess                     <joeyh@debian.org>
+Joerg Jaspert                 <joerg@debian.org>
+Mark Brown                    <broonie@debian.org>
+Martin Michlmayr              <tbm@debian.org>
+Matt Kraai                    <kraai@alumni.cmu.edu>
+Michael Beattie                       <mjb@debian.org>
+Randall Donald                <rdonald@debian.org>
+Raphael Hertzog                       <hertzog@debian.org>
+Ryan Murray                   <rmurray@debian.org>
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+Special thanks go to Jason and AJ; without their patient help, none of
+this would have been possible.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/docs/TODO b/docs/TODO
new file mode 100644 (file)
index 0000000..09a0978
--- /dev/null
+++ b/docs/TODO
@@ -0,0 +1,493 @@
+                                TODO
+                                ====
+
+[NB: I use this as a thought record/scribble, not everything on here
+     makes sense and/or is actually ever going to get done, so IIWY I
+     wouldn't use it as gospel for the future of dak or as a TODO
+     list for random hacking.]
+
+================================================================================
+
+Others
+------
+
+  o 'dak check-overrides' should remove the src-only override when a
+    binary+source override exists
+
+  o reject on > or < in a version constraint
+
+  o 'dak reject-proposed-updates' should only start an editor once to
+    capture a message; it will usually be the same message for all
+    files on the same command line.
+
+23:07 < aba> elmo: and, how about enhancing 'dak cruft-report' to spot half-dropped
+   binaries on one arch (i.e. package used to build A and B, but B is
+   no longer built on some archs)?
+
+  o tabnanny the source
+
+  o drop map-unreleased
+
+  o check email only portions of addresses match too, iff the names
+  don't, helps with the "James Troup <james@nocrew.org>"
+  vs. "<james@nocrew.org>" case.
+
+  o ensure .dsc section/prio match .changes section/prio
+
+  o 'dak clean-suites' performance is kind of crap when asked to
+     remove a lot of files (e.g. 2k or so).
+
+  o we don't handle the case where an identical orig.tar.gz is
+    mentioned in the .changes, but not in unchecked; but should we
+    care?
+
+  o 'dak ls' could do better sanity checking for -g/-G (e.g. not more
+    than one suite, etc.)
+
+  o use python2.2-tarfile (once it's in stable?) to check orig.tar.gz
+    timestamps too.
+
+  o need to decide on whether we're tying for most errors at once.. if
+    so (probably) then make sure code doesn't assume variables exist and
+    either way do something about checking error code of check_dsc and
+    later functions so we skip later checks if they're bailing.
+
+  o the .dak stuff is fundamentally braindamaged, it's not versioned
+    so there's no way to change the format, yay me.  need to fix.
+    probably by putting a version var as the first thing and checking
+    that.. auto-upgrade at least from original format would be good.
+    might also be a good idea to put everything in one big dict after
+    that?
+
+  o [?, wishlist, distant future] RFC2047-ing should be extended to
+    all headers of mails sent out.
+
+  o reject sparc64 binaries in a non '*64*' package.
+
+  o queue.py(source_exists): a) we take arguments as parameters that
+    we could figure out for ourselves (we're part of the Upload class
+    after all), b) we have this 3rd argument which defaults to "any"
+    but could in fact be dropped since no one uses it like that.
+
+  o 'dak process-unchecked': doesn't handle bin-only NMUs of stuff
+    still in NEW, BYHAND or ACCEPTED (but not the pool) - not a big
+    deal, upload can be retried once the source is in the archive, but
+    still.
+
+  o security global mail overrides should special case buildd stuff so
+    that buildds get ACCEPTED mails (or maybe 'dak security-install' (?)), that way
+    upload-security doesn't grow boundlessly.
+
+  o 'dak security-install' should upload sourceful packages first,
+     otherwise with big packages (e.g. X) and esp. when source is !i386,
+     half the arches can be uploaded without source, get copied into
+     queue/unaccepted and promptly rejected.
+
+  o 'dak cruft-report's NVIU check doesn't catch cases where source
+     package changed name, should check binaries
+     too. [debian-devel@l.d.o, 2004-02-03]
+
+  o cnf[Rm::logfile] is misnamed...
+
+<aj> i'd be kinda inclined to go with insisting the .changes file take
+   the form ---- BEGIN PGP MESSAGE --- <non -- BEGIN/END lines> --
+   BEGIN PGP SIG -- END PGP MESSAGE -- with no lines before or after,
+   and rejecting .changes that didn't match that
+
+  o 'dak cruft-report' should check for source packages not building any binaries
+
+  o 'dak control-suite' should have a diff mode that accepts diff output!
+
+  o 'dak clean-proposed-updates' doesn't deal with 'dak rm'-d
+     packages, partial replacements etc. and more.
+
+  o 'dak reject-proposed-updates' blindly deletes with no check that
+    the delete failed which it might well given we only look for
+    package/version, not package/version _in p-u_.  duh.
+
+  o 'dak rm' should remove obsolete changes when removing from p-u, or
+    at least warn.  or 'dak reject-proposed-updates' should handle it.
+
+  o need a testsuite _badly_
+
+  o 'dak process-unchecked' crashes if run as a user in -n mode when
+    orig.tar.gz is in queue/new...
+
+<elmo_home> [<random>maybe I should reject debian packages with a non-Debian origin or bugs field</>]
+<Kamion> [<random>agreed; dunno what origin does but non-Debian bugs fields would be bad]
+
+  o 'dak clean-suites' should make use of select..except select, temporary tables
+    etc. rather than looping and calling SQL every time so we can do
+    suite removal sanely (see potato-removal document)
+
+  o 'dak rm' will happily include packages in the Cc list that aren't
+    being removed...
+
+  o 'dak rm' doesn't remove udebs when removing the source they build from
+
+  o check_dsc_against_db's "delete an entry from files while you're
+    not looking" habit is Evil and Bad.
+
+  o 'dak process-new' allows you to edit the section and change the
+    component, but really shouldn't.
+
+  o 'dak rm' needs to, when not sending bug close mails, promote Cc: to
+    To: and send the mail anyways.
+
+  o the lockfile (Archive_Maintenance_In_Progress) should probably be in a conf file
+
+  o 'dak ls' should cross-check the b.source field and if it's not
+    null and s.name linked from it != the source given in
+    -S/--source-and-binary ignore.
+
+  o 'dak reject-proposed-updates' sucks; it should a) only spam d-i
+   for sourceful rejections, b) sort stuff so it rejects sourceful
+   stuff first.  the non-sourceful should probably get a form mail, c)
+   automate the non-sourceful stuff (see b).
+
+  o 'dak process-unchecked' should do q-d stuff for faster AA [ryan]
+
+  o split the morgue into source and binary so binaries can be purged first!
+
+  o per-architecture priorities for things like different arch'es
+    gcc's, silly BSD libftw, palo, etc.
+
+  o use postgres 7.2's built-in stat features to figure out how indices are used etc.
+
+  o 'dak init-archive' shouldn't be using location, it should run down suites instead
+
+  o 'dak clean-proposed-updates' needs to know about udebs
+
+  o by default hamstring dak's mail sending so that it won't send
+    anything until someone edits a script; it's been used far too
+    much to send spam atm :(
+
+  o $ftpdir/indices isn't created by 'dak init-dir' because it's not in dak.conf
+
+  o sanity check depends/recommends/suggests too?  in fact for any
+    empty field?
+
+[minor] 'dak process-accepted's copychanges, copydotdak handling
+        sucks, the per-suite thing is static for all packages, so work out
+        in advance dummy.
+
+[dak ls] # filenames ?
+[dak ls] # maintainer, component, install date (source only?), fingerprint?
+
+  o UrgencyLog stuff should minimize it's bombing out(?)
+  o Log stuff should open the log file
+
+  o 'dak queue-report' should footnote the actual notes, and also *
+    the versions with notes so we can see new versions since being
+    noted...
+
+  o 'dak queue-report' should have alternative sorting options, including reverse
+    and without or without differentiaion.
+
+  o 'dak import-users-from-passwd' should sync debadmin and ftpmaster (?)
+
+  o <drow> Can't read file.:
+  /org/security.debian.org/queue/accepted/accepted/apache-perl_1.3.9-14.1-1.21.20000309-1_sparc.dak.
+  You assume that the filenames are relative to accepted/, might want
+  to doc or fix that.
+
+  o <neuro> the orig was in NEW, the changes that caused it to be NEW
+    were pulled out in -2, and we end up with no orig in the archive
+    :(
+
+  o SecurityQueueBuild doesn't handle the case of foo_3.3woody1 with a
+   new .orig.tar.gz followed by a foo_3.3potato1 with the same
+   .orig.tar.gz; 'dak process-unchecked' sees it and copes, but the AA
+   code doesn't and can't really easily know so the potato AA dir is
+   left with no .orig.tar.gz copy.  doh.
+
+  o orig.tar.gz in accepted not handled properly (?)
+
+  o 'dak security-install' doesn't include .orig.tar.gz but it should
+
+  o permissions (paranoia, group write, etc.) configurability and overhaul
+
+  o remember duplicate copyrights in 'dak process-new' and skip them, per package
+
+  o <M>ove option for 'dak process-new' byhand proecessing
+
+  o 'dak cruft-report' could do with overrides
+
+  o database.get_location_id should handle the lack of archive_id properly
+
+  o the whole versioncmp thing should be documented
+
+  o 'dak process-new' doesn't do the right thing with -2 and -1 uploads, as you can
+    end up with the .orig.tar.gz not in the pool
+
+  o 'dak process-new' exits if you check twice (aj)
+
+  o 'dak process-new' doesn't trap signals from 'dak examine-package' properly
+
+  o queued and/or perl on sparc stable sucks - reimplement it.
+
+  o aj's bin nmu changes
+
+  o 'dak process-new':
+    * priority >> optional
+    * arch != {any,all}
+    * build-depends wrong (via 'dak compare-suites')
+    * suid
+    * conflicts
+    * notification/stats to admin daily
+    o trap 'dak examine-package' exiting
+    o distinguish binary only versus others (neuro)
+
+  o cache changes parsed from ordering (careful tho: would be caching
+    .changes from world writable incoming, not holding)
+
+  o dak doesn't recognise binonlyNMUs correctly in terms of telling
+    who their source is; source-must-exist does, but the info is not
+    propogated down.
+
+  o Fix BTS vs. dak sync issues by queueing(via BSMTP) BTS mail so
+    that it can be released on deman (e.g. ETRN to exim).
+
+  o maintainers file needs overrides
+
+    [ change override.maintainer to override.maintainer-from +
+      override.maintainer-to and have them reference the maintainers
+      table.  Then fix 'dak make-maintainers' to use them and write some scripting
+      to handle the Santiago situation. ]
+
+  o Validate Depends (et al.) [it should match  \(\s*(<<|<|<=|=|>=|>|>>)\s*<VERSIONREGEXP>\)]
+
+  o Clean up DONE; archive to tar file every 2 weeks, update tar tvzf INDEX file.
+
+  o testing-updates suite: if binary-only and version << version in
+    unstable and source-ver ~= source-ver in testing; then map
+    unstable -> testing-updates ?
+
+  o hooks or configurability for debian specific checks (e.g. check_urgency, auto-building support)
+
+  o morgue needs auto-cleaning (?)
+
+  o dak stats: two modes, all included, seperate
+  o dak stats: add non-US
+  o dak stats: add ability to control components, architectures, archives, suites
+  o dak stats: add key to expand header
+
+================================================================================
+
+queue/approved
+--------------
+
+ o What to do with multi-suite uploads?  Presumably hold in unapproved
+   and warn?  Or what?  Can't accept just for unstable or reject just
+   from stable.
+
+ o Whenever we check for anything in accepted we also need to check in
+   unapproved.
+
+ o non-sourceful uploads should go straight through if they have
+   source in accepted or the archive.
+
+ o security uploads on auric should be pre-approved.
+
+================================================================================
+
+Less Urgent
+-----------
+
+  o change utils.copy to try rename() first
+
+  o [hard, long term] unchecked -> accepted should go into the db, not
+    a suite, but similar.  this would allow dak to get even faster,
+    make 'dak ls' more useful, decomplexify specialacceptedautobuild
+    and generally be more sane.  may even be helpful to have e.g. new
+    in the DB, so that we avoid corner cases like the .orig.tar.gz
+    disappearing 'cos the package has been entirely removed but was
+    still on stayofexecution when it entered new.
+
+  o Logging [mostly done] (todo: 'dak clean-suites' (hard), .. ?)
+
+  o 'dak process-unchecked': the tar extractor class doesn't need to be redone for each package
+
+  o reverse of source-must-exist; i.e. binary-for-source-must-not-exist
+  o REJECT reminders in 'dak clean-queues'.
+  o 'dak examine-package' should check for conflicts and warn about them visavis priority [rmurray]
+  o store a list of removed/files versions; also compare against them.
+    [but be careful about scalability]
+
+  o dak examine-package: print_copyright should be a lot more intelligent
+     @ handle copyright.gz
+     @ handle copyright.ja and copyright
+     @ handle (detect at least) symlinks to another package's doc directory
+     @ handle and/or fall back on source files (?)
+
+  o To incorporate from utils:
+     @ unreject
+
+  o auto-purge out-of-date stuff from non-free/contrib so that testing and stuff works
+  o doogie's binary -> source index
+  o jt's web stuff, matt's changelog stuff (overlap)
+
+  o [Hard] Need to merge non-non-US and non-US DBs.
+
+  o experimental needs to auto clean (relative to unstable) [partial:
+   'dak cruft-report' warns about this]
+
+  o Do a checkpc(1)-a-like which sanitizes a config files.
+  o fix parse_changes()/build_file_list() to sanity check filenames
+  o saftey check and/or rename debs so they match what they should be
+
+  o Improve 'dak compare-suites'.
+  o Need to optimize all the queries by using EXAMINE and building some INDEXs.
+    [postgresql 7.2 will help here]
+  o Need to enclose all the setting SQL stuff in transactions (mostly done).
+  o Need to finish 'dak init-db' (a way to sync dak.conf and the DB)
+  o Need the ability to rebuild all other tables from dists _or_ pools (in the event of disaster) (?)
+  o Make the --help and --version options do stuff for all scripts
+
+  o 'dak make-maintainers' can't handle whitespace-only lines (for the moment, this is feature)
+
+  o generic way of saying isabinary and isadsc. (?)
+
+  o s/distribution/suite/g
+
+  o cron.weekly:
+     @ weekly postins to d-c (?)
+     @ backup of report (?)
+     @ backup of changes.tgz (?)
+
+  o --help doesn't work without /etc/dak/dak.conf (or similar) at
+    least existing.
+
+  o rename 'dak compare-suites' (clashes with existing 'dak compare-suites')...
+
+ * Harder:
+
+    o interrupting of stracing 'dak process-unchecked' causes exceptions errors from apt_inst calls
+    o dependency checking (esp. stable) (partially done)
+    o override checks sucks; it needs to track changes made by the
+      maintainer and pass them onto ftpmaster instead of warning the
+      maintainer.
+    o need to do proper rfc822 escaping of from lines (as opposed to s/\.//g)
+    o Revisit linking of binary->source in install() in dak.
+    o Fix component handling in overrides (aj)
+    o Fix lack of entires in source overrides (aj)
+    o direport misreports things as section 'devel' (? we don't use direport)
+    o vrfy check of every Maintainer+Changed-By address; valid for 3 months.
+    o binary-all should be done on a per-source, per-architecture package
+      basis to avoid, e.g. the perl-modules problem.
+    o a source-missing-diff check: if the version has a - in it, and it
+      is sourceful, it needs orig and diff, e.g. if someone uploads
+      esound_0.2.22-6, and it is sourceful, and there is no diff ->
+      REJECT (version has a dash, therefore not debian native.)
+    o check linking of .tar.gz's to .dsc's.. see proftpd 1.2.1 as an example
+    o archive needs md5sum'ed regularly, but takes too long to do all
+      in one go; make progressive or weekly.
+    o something needs to clear out .changes files from p-u when
+      removing stuff superseded by newer versions.  [but for now we have
+      'dak clean-proposed-updates']
+    o test sig checking stuff in test/ (stupid thing is not modularized due to global abuse)
+    o when encountering suspicous things (e.g. file tainting) do something more drastic
+
+ * Easy:
+
+    o suite mapping and component mapping are parsed per changes file,
+      they should probably be stored in a dictionary created at startup.
+    o don't stat/md5sum files you have entries for in the DB, moron
+      boy (Dak.check_source_blah_blah)
+    o promote changes["changes"] to mandatory in dak.py(dump_vars)
+      after a month or so (or all .dak files contain in the queue
+      contain it).
+    o 'dak rm' should behave better with -a and without -b; see
+      gcc-defaults removal for an example.
+    o Reject on misconfigured kernel-package uploads
+    o utils.extract_component_from_section: main/utils -> main/utils, main rather than utils, main
+    o Fix 'dak process-unchecked' to warn if run when not in incoming or p-u
+    o dak should validate multi-suite uploads; only possible valid one
+      is "stable unstable"
+    o cron.daily* should change umask (aj sucks)
+    o 'dak cruft-report' doesn't look at debian-installer but should.
+    o 'dak cruft-report' needs to check for binary-less source packages.
+    o 'dak cruft-report' could accept a suite argument (?)
+    o byhand stuff should send notification
+    o 'dak poolize' should udpate db; move files, not the other way around [neuro]
+    o 'dak rm' should update the stable changelog [joey]
+    o update tagdb.dia
+
+ * Bizzare/uncertain:
+
+    o drop rather dubious currval stuff (?)
+    o rationalize os.path.join() usage
+    o 'dak cruft-report' also doesn't seem to warn about missing binary packages (??)
+    o logging: hostname + pid ?
+    o ANAIS should be done in dak (?)
+    o Add an 'add' ability to 'dak rm' (? separate prog maybe)
+    o Replicate old dinstall report stuff (? needed ?)
+    o Handle the case of 1:1.1 which would overwrite 1.1 (?)
+    o maybe drop -r/--regex in 'dak ls', make it the default and
+      implement -e/--exact (a la joey's "elmo")
+    o dsc files are not checked for existence/perms (only an issue if
+      they're in the .dsc, but not the .changes.. possible?)
+
+ * Cleanups & misc:
+
+    o db_access' get_files needs to use exceptions not this None, > 0, < 0 return val BS (?)
+    o The untouchable flag doesn't stop new packages being added to ``untouchable'' suites
+
+================================================================================
+
+Packaging
+---------
+
+  o Fix stuff to look in sensible places for libs and config file in debian package (?)
+
+================================================================================
+
+                          --help      manpage
+-----------------------------------------------------------------------------
+check-archive            X
+check-overrides           X             X
+check-proposed-updates    X
+clean-proposed-updates    X
+clean-queues             X
+clean-suites             X              X
+compare-suites           X
+control-overrides         X             X
+control-suite             X             X
+cruft-report             X
+decode-dot-dak            X
+examine-package           X
+generate-releases        X
+import-archive            X
+import-users-from-passwd  X             X
+init-db                          X
+init-dirs                X
+ls                        X             X
+make-maintainers          X             X
+make-overrides            X
+make-suite-file-list      X
+poolize                   X             X
+process-accepted          X             X
+process-new               X             X
+process-unchecked         X
+queue-report              X
+rm                       X              X
+security-install          X
+stats                    X
+symlink-dists             X
+
+
+================================================================================
+
+Random useful-at-some-point SQL
+-------------------------------
+
+UPDATE files SET last_used = '1980-01-01'
+  FROM binaries WHERE binaries.architecture = <x> 
+                  AND binaries.file = files.id;
+
+DELETE FROM bin_associations 
+ WHERE EXISTS (SELECT id FROM binaries 
+                WHERE architecture = <x> 
+                  AND id = bin_associations.bin);
+
+================================================================================
diff --git a/docs/database.dia b/docs/database.dia
new file mode 100644 (file)
index 0000000..8d69080
Binary files /dev/null and b/docs/database.dia differ
diff --git a/docs/manpages/Makefile b/docs/manpages/Makefile
new file mode 100644 (file)
index 0000000..75cf3cc
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/make -f
+
+SGMLMANPAGES   = check-overrides.1.sgml clean-suites.1.sgml control-overrides.1.sgml control-suite.1.sgml import-users-from-passwd.1.sgml ls.1.sgml make-maintainers.1.sgml override.1.sgml poolize.1.sgml process-accepted.1.sgml process-new.1.sgml rm.1.sgml
+
+MANPAGES       = $(patsubst %.sgml, dak_%, $(SGMLMANPAGES))
+
+
+all: $(MANPAGES)
+
+dak_%: %.sgml
+       docbook2man $< > /dev/null
+
+clean:
+       rm -f $(MANPAGES) manpage.links manpage.log manpage.refs
diff --git a/docs/manpages/check-overrides.1.sgml b/docs/manpages/check-overrides.1.sgml
new file mode 100644 (file)
index 0000000..a4a7c14
--- /dev/null
@@ -0,0 +1,61 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_check-overrides</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak check-overrides</>
+    <refpurpose>Utility to alter or display the contents of a suite</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak check-overrides</>
+      <arg><option><replaceable>options</replaceable></></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak check-overrides</command> is a cruft checker for overrides.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+      <VarListEntry><term><option>-h/--help</option></>
+       <ListItem>
+         <Para>Show help and then exit.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Notes</>
+
+  <Para>dak check-overrides is not a good idea with New Incoming as it doesn't take into account queue/accepted.  You can minimize the impact of this by running it immediately after 'dak process-accepted' but that's still racy because 'dak process-new' doesn't lock with 'dak process-accepted'.  A better long term fix is the evil plan for accepted to be in the DB.</>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak check-overrides</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/clean-suites.1.sgml b/docs/manpages/clean-suites.1.sgml
new file mode 100644 (file)
index 0000000..621bbc3
--- /dev/null
@@ -0,0 +1,82 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_clean-suites</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak clean-suites</>
+    <refpurpose>Utility to clean out old packages</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak clean-suites</>
+      <arg><option><replaceable>options</replaceable></></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak clean-suites</command> is a utility to clean out old packages. It will clean out any binary packages not referenced by a suite and any source packages not referenced by a suite and not referenced by any binary packages.  Cleaning is not actual deletion, but rather, removal of packages from the pool to a 'morgue' directory.  The 'morgue' directory is split into dated sub-directories to keep things sane in big archives.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <variablelist>
+      <VarListEntry><term><option>-n/--no-action</option></>
+       <ListItem>
+         <Para>Don't actually clean any packages.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-h/--help</option></>
+       <ListItem>
+         <Para>Show help and then exit.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+  <refsect1>
+    <title>Configuration</title>
+    <para><command>dak clean-suites</command> uses dak's configuration file. It follows the typical ISC configuration format as seen in ISC tools like bind 8 and dhcpd.  Apart from being able to alter the defaults for command line options, the following configuration items are used:</para>
+    <variablelist>
+      <varlistentry>
+       <term>Clean-Suites::StayOfExecution</term>
+       <listitem>
+         <para>This is the number of seconds unreferenced packages are left before being cleaned.</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>Clean-Suites::MorgueSubDir</term>
+       <listitem>
+         <para>If not blank, this is the subdirectory in the morgue used to hold removed packages.</para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak clean-suites</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/control-overrides.1.sgml b/docs/manpages/control-overrides.1.sgml
new file mode 100644 (file)
index 0000000..26440ad
--- /dev/null
@@ -0,0 +1,98 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_control-overrides</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak control-overrides</>
+    <refpurpose>Utility to manipulate the packages overrides</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak control-overrides</>
+      <arg><option><replaceable>options</replaceable></option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak control-overrides</command> is the command line tool to handle override files.  Override files can be listed or updated.
+    </para>
+  </refsect1>
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+      <varlistentry>
+       <term><option>-a/--add</option></term>
+       <listitem>
+         <para>Add entries to the override DB.  Changes and deletions are ignored.</para>
+       </listitem>
+      </varlistentry>
+
+      <VarListEntry><term><option>-c/--component=<replaceable>component</replaceable></option></>
+       <ListItem><Para>Uses the override DB for the component listed.</para>
+       </listitem>
+      </VarListEntry>
+
+      <varlistentry>
+       <term><option>-h/--help</option></term>
+       <listitem>
+         <para>Display usage help and then exit.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-l/--list</option></term>
+       <listitem>
+         <para>Lists the override DB to stdout.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-q/--quiet</option></term>
+       <listitem>
+         <para>Be less verbose about what has been done.</para>
+       </listitem>
+      </varlistentry>
+
+      <VarListEntry><term><option>-s/--suite=<replaceable>suite</replaceable></option></>
+       <ListItem><Para>Uses the override DB for the suite listed.</para></listitem>
+      </varlistentry>
+
+      <VarListEntry><term><option>-S/--set</option></term>
+       <ListItem><Para>Set the override DB to the provided input.</PARA></LISTITEM>
+      </VarListEntry>
+
+      <varlistentry>
+       <term><option>-t/--type=<replaceable>type</replaceable></option></term>
+       <listitem>
+         <para>Uses the override DB for the type listed.  Possible values are: <literal>deb</literal>, <literal>udeb</literal> and <literal>dsc</literal>.</para>
+       </listitem>
+      </varlistentry>
+
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak control-overrides</command> returns zero on normal operation, non-zero on error.
+    </para>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/control-suite.1.sgml b/docs/manpages/control-suite.1.sgml
new file mode 100644 (file)
index 0000000..12c89c5
--- /dev/null
@@ -0,0 +1,82 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_control-suite</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak control-suite</>
+    <refpurpose>Utility to alter or display the contents of a suite</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak control-suite</>
+      <arg><option><replaceable>options</replaceable></></arg>
+      <arg choice="plain"><replaceable>file...</replaceable></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak control-suite</command> is a utility to alter or display the contents of a suite.  Input for alterations is taken either from filename(s) supplied or stdin.  The format for both input and output is lines each with a whitespace separated list of: <literal>package</literal>, <literal>version</literal> and <literal>architecture</literal>.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+      <VarListEntry><term><option>-a/--add=<replaceable>suite</replaceable></option></>
+       <ListItem>
+         <Para>Add to the suite.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-l/--list=<replaceable>suite</replaceable></option></>
+       <ListItem>
+         <Para>List the contents of the suite.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-r/--remove=<replaceable>suite</replaceable></option></>
+       <ListItem>
+         <Para>Remove from the suite.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-s/--set=<replaceable>suite</replaceable></option></>
+       <ListItem>
+         <Para>Set the suite to exactly the input.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-h/--help</option></>
+       <ListItem>
+         <Para>Show help and then exit.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak control-suite</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/dak.ent b/docs/manpages/dak.ent
new file mode 100644 (file)
index 0000000..1860e8e
--- /dev/null
@@ -0,0 +1,20 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+
+<!-- Boiler plate docinfo section -->
+<!ENTITY dak-docinfo "
+ <docinfo>
+   <address><email>james@nocrew.org</email></address>
+   <author><firstname>James</firstname> <surname>Troup</surname></author>
+   <copyright><year>2000-2001</year> <holder>James Troup</holder></copyright>
+   <date>15 January 2001</date>
+ </docinfo>
+"> 
+
+<!-- Boiler plate Author section -->
+<!ENTITY manauthor "
+ <RefSect1><Title>Author</title>
+   <para>
+   dak was written by James Troup <email>james@nocrew.org</email>.
+   </para>
+ </RefSect1>
+">
diff --git a/docs/manpages/import-users-from-passwd.1.sgml b/docs/manpages/import-users-from-passwd.1.sgml
new file mode 100644 (file)
index 0000000..0fd4851
--- /dev/null
@@ -0,0 +1,94 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_import-users-from-passwd</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak import-users-from-passwd</>
+    <refpurpose>Utility to sync PostgreSQL users with system users</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak import-users-from-passwd</>
+      <arg><option><replaceable>options</replaceable></></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak import-users-from-passwd</command> is a utility to sync PostgreSQL's user database with the system's users.  It is designed to allow the use of 'peer sameuser' authentication.  It simply adds any users in the password file into PostgreSQL's pg_user table if they are already not there.  It will also warn you about users who are in the pg_user table but not in the password file.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+      <VarListEntry><term><option>-n/--no-action<replaceable></replaceable></option></>
+       <ListItem>
+         <Para>Don't actually do anything.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-q/--quiet<replaceable></replaceable></option></>
+       <ListItem>
+         <Para>Be quiet, i.e. display as little output as possible.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-v/--verbose</option></>
+       <ListItem>
+         <Para>Be verbose, i.e. display more output than normal.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-h/--help</option></>
+       <ListItem>
+         <Para>Show help and then exit.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+  <refsect1>
+    <title>Configuration</title>
+    <para><command>dak import-users-from-passwd</command> uses dak's configuration file. It follows the typical ISC configuration format as seen in ISC tools like bind 8 and dhcpd.  Apart from being able to alter the defaults for command line options, the following configuration items are used:</para>
+    <variablelist>
+      <varlistentry>
+       <term>Import-Users-From-Passwd::ValidGID</term>
+       <listitem>
+         <para>Each user's primary GID is compared with this, if it's not blank.  If they match, the user will be processed, if they don't, the user will be skipped.</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>Import-Users-From-Passwd::KnownPostgresUsers</term>
+       <listitem>
+         <para>This is a comma-separated list of users who are in PostgreSQL's pg_user table but are not in the password file.</para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak import-users-from-passwd</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/ls.1.sgml b/docs/manpages/ls.1.sgml
new file mode 100644 (file)
index 0000000..c7c4f29
--- /dev/null
@@ -0,0 +1,104 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_ls</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak ls</>
+    <refpurpose>Utility to display information about packages</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak ls</>
+      <arg><option><replaceable>options</replaceable></></arg>
+      <arg choice="plain"><replaceable>package</replaceable></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak ls</command> is a utility to display information about packages, specificaly what suites they are in and for which architectures.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+      <VarListEntry><term><option>-a/--architecture=<replaceable>architecture</replaceable></option></>
+       <ListItem>
+         <Para>Only list package information for the listed architecture(s).</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-b/--binary-type=<replaceable>binary type</replaceable></option></>
+       <ListItem>
+         <Para>Only show package information for the binary type ('deb' or 'udeb').</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <varlistentry><term><option>-c/--component=<replaceable>component</replaceable></option></term>
+       <listitem>
+         <para>Only list package information for the listed component(s).</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-g/--greaterorequal</option></term>
+       <term><option>-G/--greaterthan</option></term>
+       <listitem>
+         <para>Determine the highest version of each package in the target suite (which is forced to just unstable if one was not specificed) and, in addition to the normal output, also print a line suitable for sending in a reply to a buildd as a 'dep-wait' command.  For <option>-g/--greaterorequal</option>, the versioned dependency is a >= one, e.g. <literallayout>dep-retry libgdbm-dev (>= 1.8.3-2)</literallayout></para>
+         <para>And for <option>-G/--greaterthan</option>, the versioned dependency is a >> one, e.g. <literallayout>dep-retry libflac-dev (>> 1.1.0-10)</literallayout></para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-r/--regex</option></term>
+       <listitem>
+         <para>Treat the <replaceable>package</replaceable> argument as a regex, rather than doing an exact search.</para>
+       </listitem>
+      </varlistentry>
+
+      <VarListEntry><term><option>-s/--suite=<replaceable>suite</replaceable></option></>
+       <ListItem>
+         <Para>Only list package information for the suite(s) listed.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-S/--source-and-binary</option></>
+       <ListItem>
+         <Para>For each package which is a source package, also show information about the binary packages it produces.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-h/--help</option></>
+       <ListItem>
+         <Para>Show help and then exit.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak ls</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/make-maintainers.1.sgml b/docs/manpages/make-maintainers.1.sgml
new file mode 100644 (file)
index 0000000..8cc324c
--- /dev/null
@@ -0,0 +1,85 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_make-maintainers</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak make-maintainers</>
+    <refpurpose>Utility to generate an index of package's maintainers</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak make-maintainers</>
+      <arg><option><replaceable>options</replaceable></></arg>
+      <arg choice="plain"><replaceable>extra file...</replaceable></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak make-maintainers</command> is a utility to generate an index of package's maintainers.  The output format is:
+<literallayout>package~version maintainer</literallayout>
+      The input format of extra files can either be in this form or in the old style which is similar, but lacking the version number, i.e.:
+<literallayout>package maintainer</literallayout>
+      dak Make-Maintainers will auto-detect the type of layout of the extra file.  If the extra file is in the old style format the records in it are assumed to supersed any that were seen earlier (i.e. either from earlier extra files or generated from the SQL).
+    </Para>
+    <para>
+      dak Make-Maintainers determines the maintainer of a package by comparing suite priority (see 'Configuration') and then version number.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <variablelist>
+      <VarListEntry><term><option>-h/--help</option></>
+       <ListItem>
+         <Para>Show help and then exit.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+  <refsect1>
+    <title>Configuration</title>
+    <para><command>dak make-maintainers</command> uses dak's configuration file. It follows the typical ISC configuration format as seen in ISC tools like bind 8 and dhcpd.  Apart from being able to alter the defaults for command line options, the following configuration items are used:</para>
+    <variablelist>
+      <varlistentry>
+       <term>Suite::&lt;SUITE&gt;::Priority</term>
+       <listitem>
+         <para>Suite priority overrides the version checks dak make-maintainers does.  A package in higher priority suite overrides versions in lower priority suites even if the version number in the higher priority suite is older.</para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>New versus Old output format</title>
+    <para>Converting the new output format to the old output format is easy with some simple sed + awk, e.g.
+<literallayout>sed -e "s/~[^  ]*\([   ]\)/\1/"  | awk '{printf "%-20s ", $1; for (i=2; i<=NF; i++) printf "%s ", $i; printf "\n";}'</literallayout>
+</para>
+  </refsect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak make-maintainers</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/override.1.sgml b/docs/manpages/override.1.sgml
new file mode 100644 (file)
index 0000000..12afac5
--- /dev/null
@@ -0,0 +1,87 @@
+<!-- -*- mode: sgml -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_override</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak override</>
+    <refpurpose>Make micromodifications or queries to the overrides table</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak override</>
+      <arg><option><replaceable>options</replaceable></></arg>
+      <arg choice="plain"><replaceable>package</replaceable></arg>
+      <arg><option><replaceable>section</replaceable></></arg>
+      <arg><option><replaceable>priority</replaceable></></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak override</command> makes micromodifications and queries the overrides.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+      <VarListEntry><term><option>-h/--help</option></>
+       <ListItem>
+         <Para>Show help and then exit.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+      <VarListEntry><term><option>-d/--done=<replaceable>BUG#</replaceable></option></>
+       <ListItem>
+         <Para>Close the listed bugs as part of adjusting the overrides</PARA>
+       </LISTITEM>
+      </VarListEntry>
+      <VarListEntry><term><option>-n/--no-action</option></>
+       <ListItem>
+         <Para>Show what dak override would do but make no changes</PARA>
+       </LISTITEM>
+      </VarListEntry>
+      <VarListEntry><term><option>-s/--suite=<replaceable>suite</replaceable></option></>
+       <ListItem>
+         <Para>Affect the overrides in suite listed.  The default is <literal>unstable</literal></PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Common use</>
+    <para>
+      <command>dak override</command> when invoked with only a package name will tell you what section and priority the given package has.
+    </PARA>
+    <para>
+      <command>dak override</command> when invoked with a package and one or two other values will set the section and/or priority to the values given. You may use a single period ('.') to represent "do not change" or you can ommit the value you do not want to change.
+    </PARA>
+  </RefSect1>
+  <RefSect1><Title>Notes</>
+
+  <Para><command>dak override</command> essentially lets you do what <command>dak control-overrides</command> does only on the microscopic scale rather than the macroscopic scale of <command>dak control-overrides</command>. Use with care.</>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak override</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/poolize.1.sgml b/docs/manpages/poolize.1.sgml
new file mode 100644 (file)
index 0000000..6d54d90
--- /dev/null
@@ -0,0 +1,63 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+  
+  <refmeta>
+    <refentrytitle>dak_poolize</>
+    <manvolnum>1</>
+  </refmeta>
+  
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak poolize</>
+    <refpurpose>Utility to poolize files (move them from legacy to pool location)</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak poolize</>
+      <arg><option><replaceable>options</replaceable></></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+  
+  <RefSect1><Title>Description</>
+    <para>   
+      <command>dak poolize</command> is the command line tool to poolize files; i.e. move files from legacy locations to their corresponding pool locations.
+      </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+    <VariableList>
+      <varlistentry>
+       <term><option>-l/--limit</option>=<replaceable>size in kilobytes</replaceable></term>
+       <listitem>
+         <para>Set the maximum amount of data to poolize.  <emphasis>Note:</emphasis> Without this option, all files will be poolized.</para>
+       </listitem>
+      </varlistentry>
+      
+      <varlistentry>
+       <term><option>-n/--no-action</option></term>
+       <listitem>
+         <para>Don't actually do anything, just show what would be done.</para>
+       </listitem>
+      </varlistentry>
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak poolize</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+  
+</refentry>
diff --git a/docs/manpages/process-accepted.1.sgml b/docs/manpages/process-accepted.1.sgml
new file mode 100644 (file)
index 0000000..1f3cf4e
--- /dev/null
@@ -0,0 +1,100 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_process-accepted</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak process-accepted</>
+    <refpurpose>Installs packages from accepted</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak process-accepted</>
+      <arg><option><replaceable>options</replaceable></></arg>
+      <arg choice="plain"><replaceable>changes_file</replaceable></arg>
+      <arg><option><replaceable>...</replaceable></option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak process-accepted</command> is the program which installs packages from the accepted directory into the distribution.
+    </PARA></REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+
+      <varlistentry>
+       <term><option>-a/--automatic</option></term>
+       <listitem>
+         <para>Run automatically; i.e. perform the default action if it's possible to do so without user interaction.  Intend for use in cron jobs and the like.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-h/--help</option></term>
+       <listitem>
+         <para>Display usage help and then exit.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-m/--manual-reject=<replaceable>message</replaceable></option></term>
+       <listitem>
+         <para>Perform a manual rejection of the package.  The <replaceable>message</replaceable> is included in the rejection notice sent to the uploader.  If no <replaceable>message</replaceable> is given, an editor will be spawned so one can be added to the rejection notice.
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-n/--no-action</option></term>
+       <listitem>
+         <para>Don't actually install anything; just show what would be done.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-p/--no-lock</option></term>
+       <listitem>
+         <para>Don't check the lockfile.  Obviously dangerous and should only be used for cron jobs (if at all).</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-v/--version</option></term>
+       <listitem>
+         <para>Display the version number and then exit.</para>
+       </listitem>
+      </varlistentry>
+
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak process-accepted</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  <refsect1>
+    <title>Acknowledgements</title>
+    <para>dak process-accepted is based very heavily on dinstall, written by Guy Maor <email>maor@debian.org</email>; in fact it started out life as a dinstall clone.</para>
+  </refsect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/process-new.1.sgml b/docs/manpages/process-new.1.sgml
new file mode 100644 (file)
index 0000000..f99c6cf
--- /dev/null
@@ -0,0 +1,95 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+
+  <refmeta>
+    <refentrytitle>dak_process-new</>
+    <manvolnum>1</>
+  </refmeta>
+
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak process-new</>
+    <refpurpose>Processes BYHAND and NEW packages</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak process-new</>
+      <arg><option><replaceable>options</replaceable></></arg>
+      <arg choice="plain"><replaceable>changes_file</replaceable></arg>
+      <arg><option><replaceable>...</replaceable></option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <RefSect1><Title>Description</>
+    <para>
+      <command>dak process-new</command> is the program which installs packages from the accepted directory into the distribution.
+    </PARA></REFSECT1>
+
+  <RefSect1><Title>Options</>
+
+    <VariableList>
+
+      <varlistentry>
+       <term><option>-a/--automatic</option></term>
+       <listitem>
+         <para>Run automatically; i.e. perform the default action if it's possible to do so without user interaction.  Intend for use in cron jobs and the like.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-h/--help</option></term>
+       <listitem>
+         <para>Display usage help and then exit.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-m/--manual-reject=<replaceable>message</replaceable></option></term>
+       <listitem>
+         <para>Perform a manual rejection of the package.  The <replaceable>message</replaceable> is included in the rejection notice sent to the uploader.  If no <replaceable>message</replaceable> is given, an editor will be spawned so one can be added to the rejection notice.
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-n/--no-action</option></term>
+       <listitem>
+         <para>Don't actually install anything; just show what would be done.</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-p/--no-lock</option></term>
+       <listitem>
+         <para>Don't check the lockfile.  Obviously dangerous and should only be used for cron jobs (if at all).</para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><option>-v/--version</option></term>
+       <listitem>
+         <para>Display the version number and then exit.</para>
+       </listitem>
+      </varlistentry>
+
+    </VariableList>
+  </RefSect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak process-new</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+
+</refentry>
diff --git a/docs/manpages/rm.1.sgml b/docs/manpages/rm.1.sgml
new file mode 100644 (file)
index 0000000..5b2eaf9
--- /dev/null
@@ -0,0 +1,215 @@
+<!-- -*- mode: sgml; mode: fold -*- -->
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
+
+<!ENTITY % dakent SYSTEM "dak.ent">
+%dakent;
+
+]>
+
+<refentry>
+  &dak-docinfo;
+  
+  <refmeta>
+    <refentrytitle>dak_rm</>
+    <manvolnum>1</>
+  </refmeta>
+  
+  <!-- Man page title -->
+  <refnamediv>
+    <refname>dak rm</>
+    <refpurpose>Utility to remove/add packages from suites</>
+  </refnamediv>
+
+  <!-- Arguments -->
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>dak rm</>
+      <arg><option><replaceable>options</replaceable></></arg>
+      <arg choice="plain"><replaceable>package</replaceable></arg>
+      <arg><option><replaceable>...</replaceable></option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+  
+  <RefSect1><Title>Description</>
+    <para>   
+      <command>dak rm</command> is the command line tool to add and remove package sets from suites with enforced logging, optional bug closing and override updates.
+    </PARA>
+  </REFSECT1>
+
+  <RefSect1><Title>Options</>
+    
+    <VariableList>
+      <VarListEntry><term><option>-a/--architecture=<replaceable>architecture</replaceable></option></>
+       <ListItem>
+         <Para>Restrict the packages being considered to the architecture(s) listed.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-b/--binary</option></>
+       <ListItem>
+         <Para>Only look at binary packages.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-c/--component=<replaceable>component</replaceable></option></>
+       <ListItem>
+         <Para>Restrict the packages being considered to those found in the component(s) listed.  The default is <literal>main</literal>.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-C/--carbon-copy=<replaceable>[ bug number | 'package' | email address ]</replaceable></option></>
+       <ListItem>
+         <Para>Carbon copy the bug closing mail to the address(es) given.  If the removal was not requested by the maintainer, this option should always be used to inform the maintainer of the package's removal.  3 types of address are accepted.</PARA>
+           <itemizedlist>
+             <listitem>
+               <para>number - assumed to be a bug number, and expanded to nnnnn@bugs.debian.org.</para>
+             </listitem>
+             <listitem>
+               <para>'<literal>package</literal>' - carbon copy package@package.debian.org for each package given as an argument.</para>
+             </listitem>
+             <listitem>
+               <para>anything containing '@' - assumed to be an email address, and carbon copied as is.</para>
+             </listitem>
+           </itemizedlist>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-d/--done=<replaceable>done</replaceable></option></>
+       <ListItem>
+         <Para>Close the bug(s) listed on successful completion.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-m/--reason=<replaceable>reason</replaceable></option></>
+       <ListItem>
+         <Para>The reason for the removal or addition of the package(s).  This is a required option; if not provided an editor will be spawned so the reason can be added there.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-n/--no-action</option></>
+       <ListItem>
+         <Para>Don't actually do anything; just show what would be done.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-p/--partial</option></>
+       <ListItem>
+         <Para>Partial removal of a package, so the package is not removed from the overrides.  This option is implied by <option>-a/--architecture</option>.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-R/--rdep-check</option></>
+       <ListItem>
+         <Para>Check the reverse dependencies (and build-dependencies) of the packages that are to be removed and warn if anything will break.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-s/--suite=<replaceable>suite</replaceable></option></>
+       <ListItem>
+         <Para>Only add/remove the packages from the suite(s) listed.  The default is <literal>unstable</literal></PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+      <VarListEntry><term><option>-S/--source-only</option></>
+       <ListItem>
+         <Para>Only look at source packages.</PARA>
+       </LISTITEM>
+      </VarListEntry>
+
+    </VariableList>
+  </RefSect1>
+
+    <refsect1>
+      <title>How packages are chosen</title>
+      <para>There are 3 methods for selecting packages.</para>
+      <itemizedlist>
+       <listitem>
+         <para>Source + Binaries. (default)</para>
+         <para>In this mode <command>dak rm</command> will assume each of the package(s) passed as arguments are source packages and will also remove any binary packages built from these source packages.</para>
+       </listitem>
+       <listitem>
+         <para>Binary only.</para>
+         <para>Only binary packages are searched; source packages are ignored.  This mode is chosen by use of the <option>-b/--binary</option> switch.</para>
+         <para>This should <emphasis>only</emphasis> be used for orphaned binary packages (i.e. those no longer built by source packages); otherwise, in any system (e.g. Debian) which has auto-building, pointless (and uninstallable) recompiles will be triggered.</para>
+       </listitem>
+       <listitem>
+         <para>Source only.</para>
+         <para>Only source packages are searched; binary packages are ignored.  This mode is chosen by use of the <option>-S/--source</option> switch.</para>
+       </listitem>
+      </itemizedlist>
+    </refsect1>
+
+    <refsect1>
+      <title>Configuration</title>
+      <para><command>dak rm</command> uses dak's configuration file. It follows the typical ISC configuration format as seen in ISC tools like bind 8 and dhcpd.  Apart from being able to alter the defaults for command line options, the following configuration items are used:</para>
+      <variablelist>
+       <varlistentry>
+         <term>Rm::MyEmailAddress</term>
+         <listitem>
+           <para>This is used as the From: line for bug closing mails as per the -d/--done command line switch.  It, obviously, must be a RFC-822 valid email address.</para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>Rm::LogFile</term>
+         <listitem>
+           <para>This is the (absolute) file name of the logfile that dak rm unconditionally writes too.  This can not be empty or an invalid file.</para>
+         </listitem>
+       </varlistentry>
+      </variablelist>
+    </refsect1>
+
+  <refsect1>
+    <title>Examples</title>
+      <para>The first example is of a source+binaries package removal.</para>
+      <informalexample>
+       <literallayout>
+$ dak rm -d 68136 -m "Requested by tbm@; confirmed with maintainer.  Superseded by libgmp2" gmp1   
+Working... done.
+Will remove the following packages from unstable:
+
+      gmp1 |  1.3.2-8.2 | source, alpha, hppa, arm, i386, m68k, powerpc, sparc
+  gmp1-dev |  1.3.2-8.2 | alpha, hppa, arm, i386, m68k, powerpc, sparc
+
+
+------------------- Reason -------------------
+Requested by tbm@; confirmed with maintainer.  Superseded by libgmp2
+----------------------------------------------
+
+Continue (y/N)? y
+ Deleting... done.
+$
+         </literallayout>
+      </informalexample>
+      <para>The second example is of a binary-only multi-package removal.</para>
+      <informalexample>
+       <literallayout>
+$ dak rm -d 82562 -m "Requested by paul@; NBS." -b libgtkextra{5,9,10}
+Working... done.
+Will remove the following packages from unstable:
+
+libgtkextra10 |  0.99.10-2 | alpha, i386, m68k, powerpc, sparc
+libgtkextra5 |   0.99.5-1 | alpha, i386, m68k, powerpc, sparc
+libgtkextra9 |   0.99.9-1 | alpha, i386, m68k, powerpc, sparc
+
+Will also close bugs: 82562
+
+------------------- Reason -------------------
+Requested by paul@; NBS.
+----------------------------------------------
+
+Continue (y/N)? y
+ Deleting... done.
+$
+       </literallayout>
+      </informalexample>
+  </refsect1>
+
+  <RefSect1><Title>Diagnostics</>
+    <para>
+      <command>dak rm</command> returns zero on normal operation, non-zero on error.
+    </PARA>
+  </RefSect1>
+
+  &manauthor;
+  
+</refentry>
diff --git a/docs/transitions.txt b/docs/transitions.txt
new file mode 100644 (file)
index 0000000..5b52bb3
--- /dev/null
@@ -0,0 +1,275 @@
+Contents:
+
+1. Little "Howto Use it"
+2. Explanation of how it works
+
+
+1. Little "Howto Use it"
+------------------------
+
+The input file is in YAML format. Do bnot bother with comments, they
+will be removed.
+
+The format: Dont use tabs for indentation, use spaces.
+
+Strings should be within "", but normally work without.
+Exception: Version-numbers with an epoch really do want to be in
+"". YES, THEY WANT TO (or they get interpreted in a way you dont expect
+it).
+
+Keys (The order of them does not matter, only the indentation):
+
+short_tag:    A short tag for the transition, like apt_update
+  reason:     One-line reason what is intended with it
+  source:     Source package that needs to transition
+  new:        New version of the target package
+  rm:         Name of the Release Team member responsible for this transition
+  packages:   Array of package names that are affected by this transition
+
+
+The following example wants to
+a.) update apt to version 0.7.12, the responsible Release Team member
+is Andreas Barth, and it affects some apt related packages and
+b.) wants to do something similar for lintian.
+
+apt_update:
+  packages:
+    - apt
+    - synaptic
+    - cron-apt
+    - debtags
+    - feta
+    - apticron
+    - aptitude
+  reason: "Apt needs to transition to testing to get foo and bar done"
+  source: apt
+  new: 0.7.12
+  rm: Andreas Barth
+lintian_breakage:
+  reason: "Testing a new feature"
+  source: lintian
+  new: 1.23.45~bpo40+1
+  rm: Ganneff
+  packages:
+    - lintian
+    - python-syck
+
+
+########################################################################
+########################################################################
+
+
+2. Explanation of how it works
+------------------------------
+
+Assume the following transition is defined:
+
+lintian_funtest:
+  reason: "Testing a new feature"
+  source: lintian
+  new: 1.23.45~bpo40+1
+  rm: Ganneff
+  packages:
+    - lintian
+    - python-syck
+
+Also assume the lintian situation on this archive looks like this:
+   lintian | 1.23.28~bpo.1 | sarge-backports | source, all
+   lintian | 1.23.45~bpo40+1 | etch-backports | source, all
+
+------------------------------------------------------------------------
+
+Now, I try to upload a (NEW, but that makes no difference) version of
+python-syck:
+
+$ dak process-unchecked -n python-syck_0.61.2-1~bpo40+1_i386.changes 
+
+python-syck_0.61.2-1~bpo40+1_i386.changes
+REJECT
+Rejected: python-syck: part of the lintian_funtest transition.
+
+Your package is part of a testing transition designed to get lintian migrated
+(it currently is at version 1.23.28~bpo.1, we need version 1.23.45~bpo40+1)
+
+Transition description: Testing a new feature
+
+This transition is managed by the Release Team, and Ganneff
+is the Release-Team member responsible for it.
+Please contact Ganneff or debian-release@lists.debian.org if you
+need further assistance.
+
+------------------------------------------------------------------------
+
+Lets change the definition of the transition, assume it is now:
+
+lintian_funtest:
+  reason: "Testing a new feature"
+  source: lintian
+  new: 1.22.28~bpo.1
+  rm: Ganneff
+  packages:
+    - lintian
+    - python-syck
+
+Which checks for a version older than the version actually available. Result:
+
+dak process-unchecked -n python-syck_0.61.2-1~bpo40+1_i386.changes 
+
+python-syck_0.61.2-1~bpo40+1_i386.changes
+NEW for etch-backports
+(new) python-syck_0.61.2-1~bpo40+1.diff.gz extra python
+(new) python-syck_0.61.2-1~bpo40+1.dsc extra python
+(new) python-syck_0.61.2-1~bpo40+1_i386.deb extra python
+PySyck python bindings to the Syck YAML parser kit
+ Syck is a simple YAML parser kit.
+ .
+[...] the whole stuff about a new package.
+
+------------------------------------------------------------------------
+
+For completness, change the transition to (exact existing version):
+lintian_funtest:
+  reason: "Testing a new feature"
+  source: lintian
+  new: 1.23.28~bpo.1
+  rm: Ganneff
+  packages:
+    - lintian
+
+and the result is:
+
+dak process-unchecked -n python-syck_0.61.2-1~bpo40+1_i386.changes 
+
+python-syck_0.61.2-1~bpo40+1_i386.changes
+NEW for etch-backports
+[... we know this ...]
+
+------------------------------------------------------------------------
+
+The second part is the check_transitions script.
+For that we take the following transitions as example:
+
+apt_update:
+  reason: "Apt needs to transition to testing to get foo and bar done"
+  source: apt
+  new: 0.2.12-1+b1.3
+  rm: Andreas Barth
+  packages:
+    - apt
+    - synaptic
+    - cron-apt
+    - debtags
+    - feta
+    - apticron
+    - aptitude
+lintian_funtest:
+  reason: "Testing a new feature"
+  source: lintian
+  new: 1.23.45~bpo40+1
+  rm: Ganneff
+  packages:
+    - lintian
+    - python-syck
+bar_breaks_it:
+  reason: We dont want bar to break it
+  source: bar
+  new: "9:99"
+  rm: Ganneff
+  packages:
+    - kdelibs
+    - qt4-x11
+    - libqt-perl
+
+Running check-transitions ends up with the following output:
+
+Looking at transition: lintian_funtest
+ Source:      lintian
+ New Version: 1.23.45~bpo40+1
+ Responsible: Ganneff
+ Description: Testing a new feature
+ Blocked Packages (total: 2): lintian, python-syck
+
+Apt compare says: -2
+This transition is still ongoing, we currently have version 1.23.28~bpo.1
+-------------------------------------------------------------------------
+
+Looking at transition: apt_update
+ Source:      apt
+ New Version: 0.2.12-1+b1.3
+ Responsible: Andreas Barth
+ Description: Apt needs to transition to testing to get foo and bar done
+ Blocked Packages (total: 7): apt, synaptic, cron-apt, debtags, feta, apticron, aptitude
+
+Apt compare says: 4
+This transition is over, the target package reached testing, removing
+apt wanted version: 0.2.12-1+b1.3, has 0.6.46.4-0.1~bpo.1
+-------------------------------------------------------------------------
+
+Looking at transition: bar_breaks_it
+ Source:      bar
+ New Version: 9:99
+ Responsible: Ganneff
+ Description: We dont want bar to break it
+ Blocked Packages (total: 3): kdelibs, qt4-x11, libqt-perl
+
+Transition source bar not in testing, transition still ongoing.
+-------------------------------------------------------------------------
+I: I would remove the apt_update transition
+
+
+Changing our transition definitions for lintian (keeping the rest as
+above) to
+
+lintian_funtest:
+  reason: "Testing a new feature"
+  source: lintian
+  new: 1.22.28~bpo.1
+  rm: Ganneff
+  packages:
+    - lintian
+    - python-syck
+
+now we get
+
+Looking at transition: lintian_funtest
+ Source:      lintian
+ New Version: 1.22.28~bpo.1
+ Responsible: Ganneff
+ Description: Testing a new feature
+ Blocked Packages (total: 2): lintian, python-syck
+
+Apt compare says: 1
+This transition is over, the target package reached testing, removing
+lintian wanted version: 1.22.28~bpo.1, has 1.23.28~bpo.1
+-------------------------------------------------------------------------
+
+Looking at transition: apt_update
+ Source:      apt
+ New Version: 0.2.12-1+b1.3
+ Responsible: Andreas Barth
+ Description: Apt needs to transition to testing to get foo and bar done
+ Blocked Packages (total: 7): apt, synaptic, cron-apt, debtags, feta, apticron, aptitude
+
+Apt compare says: 4
+This transition is over, the target package reached testing, removing
+apt wanted version: 0.2.12-1+b1.3, has 0.6.46.4-0.1~bpo.1
+-------------------------------------------------------------------------
+
+Looking at transition: bar_breaks_it
+ Source:      bar
+ New Version: 9:99
+ Responsible: Ganneff
+ Description: We dont want bar to break it
+ Blocked Packages (total: 3): kdelibs, qt4-x11, libqt-perl
+
+Transition source bar not in testing, transition still ongoing.
+-------------------------------------------------------------------------
+I: I would remove the lintian_funtest transition
+I: I would remove the apt_update transition
+
+
+Not using the -n switch would turn the I: in actual removals :)
+The check-transition command is meant for the release team to always run
+it when they change a transition definition. It checks if the yaml is
+valid and can be loaded (but if not the archive simply does no reject)
+and also shows a nice overview.
diff --git a/scripts/debian/byhand-di b/scripts/debian/byhand-di
new file mode 100755 (executable)
index 0000000..0a004f3
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/sh -ue
+
+if [ $# -lt 4 ]; then
+       echo "Usage: $0 filename version arch changes_file"
+       exit 1
+fi
+
+TARBALL="$1"   # Tarball to read, compressed with gzip
+VERSION="$2"
+ARCH="$3"
+CHANGES="$4"   # Changes file for the upload
+
+error() {
+       echo "$*"
+       exit 1
+}
+
+# Check validity of version number
+# Expected are: YYYYMMDD, YYYYMMDD.x, YYYYMMDD<suite>x
+if ! echo "$VERSION" | grep -Eq "^[0-9]{8}(|(\.|[a-z]+)[0-9]+)$"; then
+       error "Invalid version: '$VERSION'"
+fi
+
+# Get the target suite from the Changes file
+# NOTE: it may be better to pass this to the script as a parameter!
+SUITE="$(grep "^Distribution:" "$CHANGES" | awk '{print $2}')"
+case $SUITE in
+    "")
+       error "Error: unable to determine suite from Changes file"
+       ;;
+    unstable|sid)
+       : # nothing to do
+       ;;
+    *)
+       SUITE="${SUITE}-proposed-updates"
+       ;;
+esac
+
+# This must end with /
+TARGET="/srv/ftp.debian.org/ftp/dists/$SUITE/main/installer-$ARCH/"
+
+# Check validity of the target directory
+# This could fail, for example for new architectures; doing
+# a regular BYHAND is safer in that case
+if [ ! -d "$TARGET" ]; then
+       mkdir -p "$TARGET"
+fi
+# Check that there isn't already a directory for this version
+if [ -d "$TARGET/$VERSION" ]; then
+       error "Directory already exists: $TARGET/$VERSION"
+fi
+
+# We know all data to be in ./installer-<arch>/<version>; see if there's
+# anything else in the tarball except that and the 'current' symlink
+if tar tzf "$TARBALL" | \
+   grep -Eqv "^\./(installer-$ARCH/($VERSION/.*|current|)|)$"; then
+       error "Tarball contains unexpected contents"
+fi
+
+# Create a temporary directory where to store the images
+umask 002
+TMPDIR="$(mktemp -td byhand-di.XXXXXX)"
+
+# If we fail somewhere, cleanup the temporary directory
+cleanup() {
+        rm -rf "$TMPDIR"
+}
+trap cleanup EXIT
+
+# Extract the data into the temporary directory
+tar xzf "$TARBALL" --directory="$TMPDIR" "./installer-$ARCH/"
+
+# Check the 'current' symlink
+if [ ! -L $TMPDIR/installer-$ARCH/current ]; then
+       error "Missing 'current' symlink"
+elif [ X"$(readlink "$TMPDIR/installer-$ARCH/current")" != X"$VERSION" ]; then
+       error "Incorrect 'current' symlink"
+fi
+
+# We should have an MD5SUMS file; use that for a final check
+if [ -r "$TMPDIR/installer-$ARCH/$VERSION/images/MD5SUMS" ]; then
+       (
+               cd "$TMPDIR/installer-$ARCH/$VERSION/images"
+               md5sum -c --status MD5SUMS || error "Error while checking MD5SUMS"
+       )
+else
+       error "Missing MD5SUMS file"
+fi
+
+# Move the data to the final location
+mv "$TMPDIR/installer-$ARCH/$VERSION" "$TARGET"
+mv "$TMPDIR/installer-$ARCH/current"  "$TARGET"
+
+# Fixup permissions
+find "$TARGET/$VERSION" -type d -exec chmod 755 {} +
+find "$TARGET/$VERSION" -type f -exec chmod 644 {} +
+
+trap - EXIT
+cleanup
+
+exit 0
diff --git a/scripts/debian/byhand-dm b/scripts/debian/byhand-dm
new file mode 100755 (executable)
index 0000000..a410f70
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh -e
+
+BYHAND="$1"
+VERSION="$2"
+ARCH="$3"
+CHANGES="$4"
+
+KEYRING=/srv/keyring.debian.org/keyrings/debian-keyring.gpg
+
+DESTKR=/srv/ftp.debian.org/keyrings/debian-maintainers.gpg
+
+get_id () {
+  echo "SELECT U.name, U.uid FROM fingerprint F JOIN uid U ON (F.uid = U.id) WHERE F.fingerprint = '$1';" |
+    psql projectb -At |
+    sed 's/|\(.*\)/ <\1@debian.org>/'
+}
+
+is_allowed () {
+  echo "SELECT M.name from src_uploaders U join source S on (U.source = S.id) join maintainer M on (U.maintainer = M.id) WHERE S.source = 'debian-maintainers';" |
+    psql projectb -At | 
+    while read ALLOWED; do
+      if [ "$1" = "$ALLOWED" ]; then
+        echo yes
+       break
+      fi
+    done
+}
+
+FPRINT=$(gpgv --keyring "$KEYRING" --status-fd 3 3>&1 >/dev/null 2>&1 "$CHANGES" |
+    cut -d\  -f2,3 | grep ^VALIDSIG | head -n1 | cut -d\  -f2)
+
+ID="$(get_id "$FPRINT")"
+
+if [ "$(is_allowed "$ID")" != "yes" ]; then
+  echo "Unauthorised upload by $ID"
+  exit 1
+fi
+
+echo "Authorised upload by $ID, copying into place"
+
+OUT=$(mktemp)
+
+cp "$BYHAND" "$DESTKR"
+dak import-keyring --generate-users "dm:%s" "$DESTKR" >$OUT
+
+if [ -s "$OUT" ]; then
+  /usr/sbin/sendmail -odq -oi -t <<EOF
+From: $ID
+To: <debian-project@lists.debian.org>
+Subject: Updated Debian Maintainers Keyring
+Content-Type: text/plain; charset=utf-8
+MIME-Version: 1.0
+
+With the upload of debian-maintainers version $VERSION, the following
+changes to the keyring have been made:
+
+$(cat $OUT)
+
+A summary of all the changes in this upload follows.
+
+Debian distribution maintenance software,
+on behalf of,
+$ID
+
+$(cat $CHANGES)
+EOF
+fi
+rm -f "$OUT"
+
+exit 0
diff --git a/scripts/debian/byhand-tag b/scripts/debian/byhand-tag
new file mode 100755 (executable)
index 0000000..e9cb43a
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh -ue
+
+# Tarball to read, compressed with gzip
+INPUT="${1:?"Usage: $0 filename"}"
+
+# Regular expression used to validate tag lines
+CHECKRE='^[a-z0-9A-Z.+-]+[[:space:]]+Tag[[:space:]]+[a-z0-9:. ,{}+-]+$'
+
+# This must end with /
+TARGET=/srv/ftp.debian.org/scripts/external-overrides/
+
+# Read the main directory from the tarball
+DIR="`tar ztf \"$INPUT\" | tac | tail -n 1`"
+
+# Create temporary files where to store the validated data
+umask 002
+OUTMAIN="`mktemp \"$TARGET\"tag.new.XXXXXX`"
+OUTCONTRIB="`mktemp \"$TARGET\"tag.contrib.new.XXXXXX`"
+OUTNONFREE="`mktemp \"$TARGET\"tag.non-free.new.XXXXXX`"
+
+# If we fail somewhere, cleanup the temporary files
+cleanup() {
+        rm -f "$OUTMAIN"
+        rm -f "$OUTCONTRIB"
+        rm -f "$OUTNONFREE"
+}
+trap cleanup EXIT
+
+# Extract the data into the temporary files
+tar -O -zxf "$INPUT" "$DIR"tag | grep -E "$CHECKRE" > "$OUTMAIN"
+tar -O -zxf "$INPUT" "$DIR"tag.contrib | grep -E "$CHECKRE" > "$OUTCONTRIB"
+tar -O -zxf "$INPUT" "$DIR"tag.non-free | grep -E "$CHECKRE" > "$OUTNONFREE"
+
+# Move the data to the final location
+mv "$OUTMAIN"           "$TARGET"tag
+mv "$OUTCONTRIB"        "$TARGET"tag.contrib
+mv "$OUTNONFREE"        "$TARGET"tag.non-free
+
+chmod 644 "$TARGET"tag "$TARGET"tag.contrib "$TARGET"tag.non-free
+
+(cd $TARGET && ./mk-extra-overrides.sh)
+
+trap - EXIT
+
+exit 0
diff --git a/scripts/debian/byhand-task b/scripts/debian/byhand-task
new file mode 100755 (executable)
index 0000000..8caf942
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/sh -ue
+
+if [ $# -lt 4 ]; then
+        echo "Usage: $0 filename version arch changes_file"
+        exit 1
+fi
+
+INPUT="$1"      # Tarball to read, compressed with gzip
+VERSION="$2"
+ARCH="$3"
+CHANGES="$4"    # Changes file for the upload
+
+error() {
+        echo "$*"
+        exit 1
+}
+
+# Get the target suite from the Changes file
+# NOTE: it may be better to pass this to the script as a parameter!
+SUITE="$(grep "^Distribution:" "$CHANGES" | awk '{print $2}')"
+case $SUITE in
+    "")
+        error "Error: unable to determine suite from Changes file"
+        ;;
+    unstable|sid)
+        : # OK for automated byband processing
+        ;;
+    *)
+        error "Reject: task overrides can only be processed automatically for uploads to unstable"
+        ;;
+esac
+
+
+# Regular expression used to validate tag lines
+CHECKRE='^[a-z0-9A-Z.+-]+[[:space:]]+Task[[:space:]]+[a-z0-9:. ,{}+-]+$'
+
+# This must end with /
+TARGET=/srv/ftp.debian.org/scripts/external-overrides/
+
+# Read the main directory from the tarball
+DIR="`tar ztf \"$INPUT\" | tac | tail -n 1`"
+
+# Create temporary files where to store the validated data
+umask 002
+OUTMAIN="`mktemp \"$TARGET\"task.new.XXXXXX`"
+
+# If we fail somewhere, cleanup the temporary files
+cleanup() {
+        rm -f "$OUTMAIN"
+}
+trap cleanup EXIT
+
+# Extract the data into the temporary files
+tar -O -zxf "$INPUT" "$DIR"task | grep -E "$CHECKRE" > "$OUTMAIN"
+
+# Move the data to the final location
+mv "$OUTMAIN"           "$TARGET"task
+
+chmod 644 "$TARGET"task
+
+(cd $TARGET && ./mk-extra-overrides.sh)
+
+trap - EXIT
+
+exit 0
diff --git a/scripts/debian/copyoverrides b/scripts/debian/copyoverrides
new file mode 100755 (executable)
index 0000000..a90db62
--- /dev/null
@@ -0,0 +1,29 @@
+#! /bin/sh
+
+set -e
+. $SCRIPTVARS
+echo 'Copying override files into public view ...'
+
+for f in $copyoverrides ; do
+       cd $overridedir
+       chmod g+w override.$f
+
+       cd $indices
+       rm -f .newover-$f.gz
+       pc="`gzip 2>&1 -9nv <$overridedir/override.$f >.newover-$f.gz`"
+       set +e
+       nf=override.$f.gz
+       cmp -s .newover-$f.gz $nf
+       rc=$?
+       set -e
+        if [ $rc = 0 ]; then
+               rm -f .newover-$f.gz
+       elif [ $rc = 1 -o ! -f $nf ]; then
+               echo "   installing new $nf $pc"
+               mv -f .newover-$f.gz $nf
+               chmod g+w $nf
+       else
+               echo $? $pc
+               exit 1
+       fi
+done
diff --git a/scripts/debian/ddtp-i18n-check.sh b/scripts/debian/ddtp-i18n-check.sh
new file mode 100755 (executable)
index 0000000..c42286c
--- /dev/null
@@ -0,0 +1,371 @@
+#!/bin/bash
+#
+# $Id: ddtp_i18n_check.sh 1186 2008-08-12 18:31:25Z faw $
+# 
+# Copyright (C) 2008, Felipe Augusto van de Wiel <faw@funlabs.org>
+# Copyright (C) 2008, Nicolas François <nicolas.francois@centraliens.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# On Debian systems, you can find the full text of the license in
+# /usr/share/common-licenses/GPL-2
+
+set -eu
+export LC_ALL=C
+
+# This must be defined to either 0 or 1
+# When DEBUG=0, fail after the first error.
+# Otherwise, list all the errors.
+DEBUG=0
+
+#STABLE="etch"
+TESTING="lenny"
+UNSTABLE="sid"
+
+# Original SHA256SUMS, generated by i18n.debian.net
+SHA256SUMS="SHA256SUMS"
+
+# DAK Timestamp
+TIMESTAMP="timestamp"
+
+# These special files must exist on the top of dists_parent_dir
+SPECIAL_FILES="$SHA256SUMS $TIMESTAMP $TIMESTAMP.gpg"
+
+usage () {
+       echo "Usage: $0 <dists_parent_dir> [<packages_lists_directory>]" >&2
+       exit 1
+}
+
+if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [ ! -d $1 ]
+then
+       usage
+fi
+
+# Temporary working directory. We need a full path to reduce the
+# complexity of checking SHA256SUMS and cleaning/removing TMPDIR
+TEMP_WORK_DIR=$(mktemp -d -t ddtp_dinstall_tmpdir.XXXXXX)
+cd "$TEMP_WORK_DIR"
+TMP_WORK_DIR=$(pwd)
+cd "$OLDPWD"
+unset TEMP_WORK_DIR
+
+# If it's traped, something bad happened.
+trap_exit () {
+       rm -rf "$TMP_WORK_DIR"
+       rm -f "$dists_parent_dir"/dists/*/main/i18n/Translation-*.{bz2,gz}
+       exit 1
+}
+trap trap_exit EXIT HUP INT QUIT TERM
+
+# If no argument indicates the PACKAGES_LISTS_DIR then use '.'
+PACKAGES_LISTS_DIR=${2:-.}
+
+if [ ! -d "$PACKAGES_LISTS_DIR" ]
+then
+       usage
+fi
+
+# Removing trailing /
+dists_parent_dir=${1%/}
+
+is_filename_okay () {
+       ifo_file="$1"
+
+       # Check that the file in on an "i18n" directory
+       # This ensures that the Translation-$lang files are not e.g. in
+       # dists/etch/ or dists/etch/main/
+       ifo_d=$(basename $(dirname "$ifo_file"))
+       if [ "x$ifo_d" = "xi18n" ]; then
+
+               # Check that the file is named Translation-$lang
+               ifo_f=$(basename "$ifo_file")
+               case "$ifo_f" in
+                       Translation-[a-z][a-z][a-z]_[A-Z][A-Z]) return 0;;
+                       Translation-[a-z][a-z]_[A-Z][A-Z])      return 0;;
+                       Translation-[a-z][a-z][a-z])            return 0;;
+                       Translation-[a-z][a-z])                 return 0;;
+               esac
+       fi
+
+       return 1
+}
+
+# Check a directory name against a directory whitelist 
+is_dirname_okay () {
+       ido_dir="$1"
+
+       case "$ido_dir" in
+               "$dists_parent_dir")                               return 0;;
+               "$dists_parent_dir/dists")                         return 0;;
+# TODO/FIXME: It is undecided how to update at stable/point-releases, so we
+#             don't allow files to $STABLE.
+#              "$dists_parent_dir/dists/$STABLE")                 return 0;;
+#              "$dists_parent_dir/dists/$STABLE/main")            return 0;;
+#              "$dists_parent_dir/dists/$STABLE/main/i18n")       return 0;;
+#              "$dists_parent_dir/dists/$STABLE/contrib")         return 0;;
+#              "$dists_parent_dir/dists/$STABLE/contrib/i18n")    return 0;;
+#              "$dists_parent_dir/dists/$STABLE/non-free")        return 0;;
+#              "$dists_parent_dir/dists/$STABLE/non-free/i18n")   return 0;;
+               "$dists_parent_dir/dists/$TESTING")                return 0;;
+               "$dists_parent_dir/dists/$TESTING/main")           return 0;;
+               "$dists_parent_dir/dists/$TESTING/main/i18n")      return 0;;
+               "$dists_parent_dir/dists/$TESTING/contrib")        return 0;;
+               "$dists_parent_dir/dists/$TESTING/contrib/i18n")   return 0;;
+               "$dists_parent_dir/dists/$TESTING/non-free")       return 0;;
+               "$dists_parent_dir/dists/$TESTING/non-free/i18n")  return 0;;
+               "$dists_parent_dir/dists/$UNSTABLE")               return 0;;
+               "$dists_parent_dir/dists/$UNSTABLE/main")          return 0;;
+               "$dists_parent_dir/dists/$UNSTABLE/main/i18n")     return 0;;
+               "$dists_parent_dir/dists/$UNSTABLE/contrib")       return 0;;
+               "$dists_parent_dir/dists/$UNSTABLE/contrib/i18n")  return 0;;
+               "$dists_parent_dir/dists/$UNSTABLE/non-free")      return 0;;
+               "$dists_parent_dir/dists/$UNSTABLE/non-free/i18n") return 0;;
+       esac
+
+       return 1
+}
+
+has_valid_fields () {
+       hvf_file="$1"
+       hvf_lang=${hvf_file/*-}
+
+awk "
+function print_status () {
+       printf (\"p: %d, m: %d, s: %d, l: %d\n\", package, md5, s_description, l_description)
+}
+BEGIN {
+       package       = 0 # Indicates if a Package field was found
+       md5           = 0 # Indicates if a Description-md5 field was found
+       s_description = 0 # Indicates if a short description was found
+       l_description = 0 # Indicates if a long description was found
+
+       failures      = 0 # Number of failures (debug only)
+       failed        = 0 # Failure already reported for the block
+}
+
+/^Package: / {
+       if (0 == failed) {
+               if (   (0 != package)       \
+                   || (0 != md5)           \
+                   || (0 != s_description) \
+                   || (0 != l_description)) {
+                       printf (\"Package field unexpected in $hvf_file (line %d)\n\", NR)
+                       print_status()
+                       failed = 1
+                       if ($DEBUG) { failures++ } else { exit 1 }
+               }
+               package++
+       }
+       # Next input line
+       next
+}
+
+/^Description-md5: / {
+       if (0 == failed) {
+               if (   (1 != package)       \
+                   || (0 != md5)           \
+                   || (0 != s_description) \
+                   || (0 != l_description)) {
+                       printf (\"Description-md5 field unexpected in $hvf_file (line %d)\n\", NR)
+                       print_status()
+                       failed = 1
+                       if ($DEBUG) { failures++ } else { exit 1 }
+               }
+               md5++
+       }
+       # Next input line
+       next
+}
+
+/^Description-$hvf_lang: / {
+       if (0 == failed) {
+               if (   (1 != package)       \
+                   || (1 != md5)           \
+                   || (0 != s_description) \
+                   || (0 != l_description)) {
+                       printf (\"Description-$hvf_lang field unexpected in $hvf_file (line %d)\n\", NR)
+                       print_status()
+                       failed = 1
+                       if ($DEBUG) { failures++ } else { exit 1 }
+               }
+               s_description++
+       }
+       # Next input line
+       next
+}
+
+/^ / {
+       if (0 == failed) {
+               if (   (1 != package)       \
+                   || (1 != md5)           \
+                   || (1 != s_description)) {
+                       printf (\"Long description unexpected in $hvf_file (line %d)\n\", NR)
+                       print_status()
+                       failed = 1
+                       if ($DEBUG) { failures++ } else { exit 1 }
+               }
+               l_description = 1 # There can be any number of long description
+                                 # lines. Do not count.
+       }
+       # Next line
+       next
+}
+
+/^$/ {
+       if (0 == failed) {
+               if (   (1 != package)       \
+                   || (1 != md5)           \
+                   || (1 != s_description) \
+                   || (1 != l_description)) {
+                       printf (\"End of block unexpected in $hvf_file (line %d)\n\", NR)
+                       print_status()
+                       failed = 1
+                       if ($DEBUG) { failures++ } else { exit 1 }
+               }
+       }
+
+       # Next package
+       package = 0; md5 = 0; s_description = 0; l_description = 0
+       failed = 0
+
+       # Next input line
+       next
+}
+
+# Anything else: fail
+{
+       printf (\"Unexpected line '\$0' in $hvf_file (line %d)\n\", NR)
+       print_status()
+       failed = 1
+       if ($DEBUG) { failures++ } else { exit 1 }
+}
+
+END {
+       if (0 == failed) {
+               # They must be all set to 0 or all set to 1
+               if (   (   (0 == package)        \
+                       || (0 == md5)            \
+                       || (0 == s_description)  \
+                       || (0 == l_description)) \
+                   && (   (0 != package)        \
+                       || (0 != md5)            \
+                       || (0 != s_description)  \
+                       || (0 != l_description))) {
+                       printf (\"End of file unexpected in $hvf_file (line %d)\n\", NR)
+                       print_status()
+                       exit 1
+               }
+       }
+
+       if (failures > 0) {
+               exit 1
+       }
+}
+" "$hvf_file" || return 1
+
+       return 0
+}
+
+# $SPECIAL_FILES must exist
+for sf in $SPECIAL_FILES; do
+       if [ ! -f "$dists_parent_dir/$sf" ]; then
+               echo "Special file ($sf) doesn't exist"
+               exit 1;
+       fi
+done
+
+# Comparing SHA256SUMS
+# We don use -c because a file could exist in the directory tree and not in
+# the SHA256SUMS, so we sort the existing SHA256SUMS and we create a new one
+# already sorted, if cmp fails then files are different and we don't want to
+# continue.
+cd "$dists_parent_dir"
+find dists -type f -print0 |xargs --null sha256sum > "$TMP_WORK_DIR/$SHA256SUMS.new"
+sort "$SHA256SUMS" > "$TMP_WORK_DIR/$SHA256SUMS.sorted"
+sort "$TMP_WORK_DIR/$SHA256SUMS.new" > "$TMP_WORK_DIR/$SHA256SUMS.new.sorted"
+if ! cmp --quiet "$TMP_WORK_DIR/$SHA256SUMS.sorted" "$TMP_WORK_DIR/$SHA256SUMS.new.sorted"; then
+       echo "Failed to compare the SHA256SUMS, they are not identical!" >&2
+       diff -au "$TMP_WORK_DIR/$SHA256SUMS.sorted" "$TMP_WORK_DIR/$SHA256SUMS.new.sorted" >&2
+       exit 1
+fi
+cd "$OLDPWD"
+
+# Get the list of valid packages (sorted, uniq)
+for t in "$TESTING" "$UNSTABLE"; do
+       if [ ! -f "$PACKAGES_LISTS_DIR/$t" ]; then
+               echo "Missing $PACKAGES_LISTS_DIR/$t" >&2
+               exit 1
+       fi
+       cut -d' ' -f 1 "$PACKAGES_LISTS_DIR/$t" | sort -u > "$TMP_WORK_DIR/$t.pkgs"
+done
+
+/usr/bin/find "$dists_parent_dir" |
+while read f; do
+       if   [ -d "$f" ]; then
+               if ! is_dirname_okay "$f"; then
+                       echo "Wrong directory name: $f" >&2
+                       exit 1
+               fi
+       elif [ -f "$f" ]; then
+               # If $f is in $SPECIAL_FILES, we skip to the next loop because
+               # we won't check it for format, fields and encoding.
+               for sf in $SPECIAL_FILES; do
+                       if [ "$f" = "$dists_parent_dir/$sf" ]; then
+                               continue 2
+                       fi
+               done
+
+               if ! is_filename_okay "$f"; then
+                       echo "Wrong file: $f" >&2
+                       exit 1
+               fi
+
+               # Check that all entries contains the right fields
+               if ! has_valid_fields "$f"; then
+                       echo "File $f has an invalid format" >&2
+                       exit 1
+               fi
+
+               # Check that every packages in Translation-$lang exists
+               TPKGS=$(basename "$f").pkgs
+               grep "^Package: " "$f" | cut -d' ' -f 2 | sort -u > "$TMP_WORK_DIR/$TPKGS"
+               case "$f" in
+                       */$TESTING/*)  t="$TESTING";;
+                       */$UNSTABLE/*) t="$UNSTABLE";;
+               esac
+               if diff "$TMP_WORK_DIR/$t.pkgs" "$TMP_WORK_DIR/$TPKGS" | grep -q "^>"; then
+                       diff -au "$TMP_WORK_DIR/$t.pkgs" "$TMP_WORK_DIR/$TPKGS" |grep "^+"
+                       echo "$f contains packages which are not in $t" >&2
+                       exit 1
+               fi
+
+               # Check encoding
+               iconv -f utf-8 -t utf-8 < "$f" > /dev/null 2>&1 || {
+                       echo "$f is not an UTF-8 file" >&2
+                       exit 1
+               }
+
+               # We do not check if the md5 in Translation-$lang are
+               # correct.
+
+               # Now generate files
+               #   Compress the file
+               bzip2 -c "$f" > "$f.bz2"
+               gzip  -c "$f" > "$f.gz"
+       else
+               echo "Neither a file or directory: $f" >&2
+               exit 1
+       fi
+done || false
+# The while will just fail if an internal check "exit 1", but the script
+# is not exited. "|| false" makes the script fail (and exit) in that case.
+
+echo "$dists_parent_dir structure validated successfully ($(date +%c))"
+
+# If we reach this point, everything went fine.
+trap - EXIT
+rm -rf "$TMP_WORK_DIR"
+
diff --git a/scripts/debian/dm-monitor b/scripts/debian/dm-monitor
new file mode 100755 (executable)
index 0000000..87846b7
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+echo "Known debian maintainers:"
+
+psql --html projectb <<EOF
+  SELECT uid.uid, uid.name, f.fingerprint
+    FROM uid LEFT OUTER JOIN fingerprint f ON (uid.id = f.uid) 
+   WHERE uid.uid LIKE 'dm:%'
+ORDER BY uid.uid;
+EOF
+
+echo "Debian maintainers not able to update any packages:"
+
+psql --html projectb <<EOF
+  SELECT uid.uid, uid.name
+    FROM uid 
+   WHERE uid.uid LIKE 'dm:%'
+     AND uid.uid NOT IN (
+              SELECT u.uid
+                FROM src_uploaders su JOIN source s ON (su.source = s.id)
+                     JOIN src_associations sa ON (s.id = sa.source)
+                     JOIN maintainer m ON (su.maintainer = m.id)
+                     JOIN uid u ON 
+                      (m.name LIKE u.name || ' <%>' OR
+                       m.name LIKE '% <' || substring(u.uid FROM 4) || '>')
+               WHERE u.uid LIKE 'dm:%' AND sa.suite = 5
+         )
+ORDER BY uid.uid;
+EOF
+
+echo "Packages debian maintainers may update:"
+
+psql --html projectb <<EOF
+  SELECT s.source, s.version, u.uid
+    FROM src_uploaders su JOIN source s ON (su.source = s.id) 
+         JOIN src_associations sa ON (s.id = sa.source)
+         JOIN maintainer m ON (su.maintainer = m.id)
+         JOIN uid u ON (m.name LIKE u.name || ' <%>' OR
+                       m.name LIKE '% <' || substring(u.uid FROM 4) || '>')
+   WHERE u.uid LIKE 'dm:%' AND sa.suite = 5
+ORDER BY u.uid, s.source, s.version;
+EOF
+
+echo "Source packages in the pool uploaded by debian maintainers:"
+
+psql --html projectb <<EOF
+  SELECT s.source, s.version, s.install_date, u.uid
+    FROM source s JOIN fingerprint f ON (s.sig_fpr = f.id) 
+         JOIN uid u ON (f.uid = u.id)
+   WHERE u.uid LIKE 'dm:%'
+ORDER BY u.uid, s.source, s.version;
+EOF
+
+echo "Binary packages in the pool uploaded by debian maintainers:"
+
+psql --html projectb <<EOF
+  SELECT b.package, b.version, a.arch_string AS arch, u.uid
+    FROM binaries b JOIN architecture a ON (b.architecture = a.id)
+         JOIN fingerprint f ON (b.sig_fpr = f.id) 
+         JOIN uid u ON (f.uid = u.id)
+   WHERE u.uid LIKE 'dm:%'
+ORDER BY u.uid, b.package, b.version;
+EOF
+
+echo "Recorded Uploaders:"
+
+psql --html projectb <<EOF
+  SELECT s.source, s.version, m.name
+    FROM src_uploaders su JOIN source s ON (su.source = s.id) 
+         JOIN maintainer m ON (su.maintainer = m.id)
+ORDER BY m.name, s.source, s.version;
+EOF
+
+echo "Keys without a recorded uid:"
+
+psql --html projectb <<EOF
+  SELECT *
+    FROM fingerprint f
+   WHERE f.uid IS NULL;
+EOF
+
diff --git a/scripts/debian/expire_dumps b/scripts/debian/expire_dumps
new file mode 100755 (executable)
index 0000000..9fa6ade
--- /dev/null
@@ -0,0 +1,143 @@
+#!/usr/bin/python
+
+# Copyright (C) 2007 Florian Reitmeir <florian@reitmeir.org>
+# Copyright (C) 2008 Joerg Jaspert <joerg@debian.org>
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# requires: python-dateutil
+
+import glob, os, sys
+import time, datetime
+import re
+from datetime import datetime
+from datetime import timedelta
+from optparse import OptionParser
+
+RULES = [
+    {'days':14,   'interval':0},
+    {'days':31,   'interval':7},
+    {'days':365,  'interval':31},
+    {'days':3650, 'interval':365},
+
+    # keep 14 days, all each day
+    # keep 31 days, 1 each 7th day
+    # keep 365 days, 1 each 31th day
+]
+
+TODAY = datetime.today()
+VERBOSE = False
+NOACTION = False
+PRINT = False
+PREFIX = ''
+PATH = ''
+
+def all_files(pattern, search_path, pathsep=os.pathsep):
+    """ Given a search path, yield all files matching the pattern. """
+    for path in search_path.split(pathsep):
+        for match in glob.glob(os.path.join(path, pattern)):
+            yield match
+
+def parse_file_dates(list):
+    out = []
+    # dump_2006.05.02-11:52:01.bz2
+    p = re.compile('^\./dump_([0-9]{4})\.([0-9]{2})\.([0-9]{2})-([0-9]{2}):([0-9]{2}):([0-9]{2})(.bz2)?$')
+    for file in list:
+        m = p.search(file)
+        if m:
+            d = datetime(int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)), int(m.group(5)), int(m.group(6)))
+            out.append({'name': file, 'date': d})
+    return out
+
+def prepare_rules(rules):
+    out = []
+    for rule in rules:
+        out.append(
+            {
+            'days':timedelta(days=rule['days']),
+            'interval':timedelta(days=rule['interval'])}
+            )
+    return out
+
+def expire(rules, list):
+    t_rules=prepare_rules(rules)
+    rule = t_rules.pop(0)
+    last = list.pop(0)
+
+    for file in list:
+        if VERBOSE:
+            print "current file to expire: " + file['name']
+            print file['date']
+
+        # check if rule applies
+        if (file['date'] < (TODAY-rule['days'])):
+            if VERBOSE:
+                print "move to next rule"
+            if t_rules:
+                rule = t_rules.pop(0)
+
+        if (last['date'] - file['date']) < rule['interval']:
+            if VERBOSE:
+                print "unlink file:" + file['name']
+            if PRINT:
+                print file['name']
+            if not NOACTION:
+                os.unlink(file['name'])
+        else:
+            last = file
+            if VERBOSE:
+                print "kept file:" + file['name']
+
+
+parser = OptionParser()
+parser.add_option("-d", "--directory", dest="directory",
+                  help="directory name", metavar="Name")
+parser.add_option("-f", "--pattern", dest="pattern",
+                  help="Pattern maybe some glob", metavar="*.backup")
+parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+                  help="verbose")
+parser.add_option("-n", "--no-action", action="store_true", dest="noaction", default=False,
+                  help="just prints what would be done, this implies verbose")
+parser.add_option("-p", "--print", action="store_true", dest="printfiles", default=False,
+                  help="just print the filenames that should be deleted, this forbids verbose")
+
+(options, args) = parser.parse_args()
+
+if (not options.directory):
+    parser.error("no directory to check given")
+
+if options.noaction:
+    VERBOSE=True
+    NOACTION=True
+
+if options.verbose:
+    VERBOSE=True
+
+if options.printfiles:
+    VERBOSE=False
+    PRINT=True
+
+files = sorted( list(all_files(options.pattern,options.directory)), reverse=True );
+
+if not files:
+    sys.exit(0)
+
+files_dates =  parse_file_dates(files);
+expire(RULES, files_dates)
diff --git a/scripts/debian/generate-d-i b/scripts/debian/generate-d-i
new file mode 100755 (executable)
index 0000000..fee0ba5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Original written from Jeroen van Wolffelaar <jeroen@debian.org>
+
+set -e
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+
+export PAGER=cat
+
+exec > $webdir/d-i 2>&1
+
+
+echo "udeb's in testing that don't (anymore) correspond to any testing source:"
+psql projectb -c "select b.package, b.version, (SELECT arch_string from
+architecture where b.architecture=architecture.id) as arch, s.source from
+bin_associations ba LEFT JOIN binaries b on (ba.bin=b.id) LEFT JOIN source s
+on (b.source=s.id) WHERE ba.suite=4 AND s.id NOT IN (SELECT source from
+src_associations WHERE suite=4) AND b.type = 'udeb' ORDER BY s.source,
+b.package, b.architecture;"
+
+echo "udeb's in unstable that should be in testing too:"
+psql projectb -c "select b.package, b.version, (SELECT arch_string from
+architecture where b.architecture=architecture.id) as arch, s.source from
+bin_associations ba LEFT JOIN binaries b on (ba.bin=b.id) LEFT JOIN source s
+on (b.source=s.id) WHERE ba.suite=5 AND NOT EXISTS (SELECT 1 FROM
+bin_associations ba2 WHERE ba2.suite=4 AND ba2.bin=ba.bin) AND s.id IN (SELECT
+source from src_associations WHERE suite=4) AND b.type = 'udeb' AND
+b.architecture NOT IN (4,8,12) ORDER BY s.source, b.package, b.architecture;"
+
+echo "udeb's in t-p-u that should be in testing too:"
+psql projectb -c "select b.package, b.version, (SELECT arch_string from
+architecture where b.architecture=architecture.id) as arch, s.source from
+bin_associations ba LEFT JOIN binaries b on (ba.bin=b.id) LEFT JOIN source s
+on (b.source=s.id) WHERE ba.suite=3 AND NOT EXISTS (SELECT 1 FROM
+bin_associations ba2 WHERE ba2.suite=4 AND ba2.bin=ba.bin) AND s.id IN (SELECT
+source from src_associations WHERE suite=4) AND b.type = 'udeb' AND
+b.architecture NOT IN (4,8,12) ORDER BY s.source, b.package, b.architecture;"
diff --git a/scripts/debian/import_testing.sh b/scripts/debian/import_testing.sh
new file mode 100755 (executable)
index 0000000..6b5fa6c
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# Copyright (C) 2008 Joerg Jaspert <joerg@debian.org>
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+set -e
+
+# Load up some standard variables
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+
+# What file we look at.
+TESTINGINPUT="/srv/release.debian.org/britney/Heidi/set/current"
+
+# Change to a known safe location
+cd $masterdir
+
+echo "Importing new data for testing into projectb"
+
+# Now load the data
+cat $TESTINGINPUT | dak control-suite --set testing
+
+echo "Done"
+
+exit 0
diff --git a/scripts/debian/insert_missing_changedby.py b/scripts/debian/insert_missing_changedby.py
new file mode 100644 (file)
index 0000000..570b547
--- /dev/null
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Adds yet unknown changedby fields when this column is added to an existing
+# database. If everything goes well, it needs to be run only once. Data is
+# extracted from Filippo Giunchedi's upload-history project, get the file at
+# merkel:/home/filippo/upload-history/*.db.
+
+# Copyright (C) 2008  Christoph Berg <myon@debian.org>
+# Copyright (C) 2008  Bernd Zeimetz <bzed@debian.org>
+
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+###############################################################################
+
+#    /Everybody stand back/
+#
+#    I know regular expressions
+
+###############################################################################
+
+import errno, fcntl, os, sys, time, re
+import apt_pkg
+import daklib.database
+import daklib.queue
+import daklib.utils
+from pysqlite2 import dbapi2 as sqlite
+import pysqlite2.dbapi2
+import psycopg2
+
+projectB = None
+projectBdb = None
+DBNAME = "uploads-queue.db"
+sqliteConn = None
+maintainer_id_cache={}
+
+###############################################################################
+
+def get_or_set_maintainer_id (maintainer):
+    global maintainer_id_cache
+
+    if maintainer_id_cache.has_key(maintainer):
+        return maintainer_id_cache[maintainer]
+
+    if isinstance(maintainer, basestring):
+        if not isinstance(maintainer, unicode):
+            try:
+                maintainer = unicode(maintainer, 'utf-8')
+            except:
+                maintainer = unicode(maintainer, 'iso8859-15')
+    maintainer = maintainer.encode('utf-8')
+
+    print "%s" % maintainer
+    cursor = projectBdb.cursor()
+    cursor.execute("SELECT id FROM maintainer WHERE name=%s", (maintainer, ))
+    row = cursor.fetchone()
+    if not row:
+        cursor.execute("INSERT INTO maintainer (name) VALUES (%s)" , (maintainer, ))
+        cursor.execute("SELECT id FROM maintainer WHERE name=%s", (maintainer, ))
+        row = cursor.fetchone()
+    maintainer_id = row[0]
+    maintainer_id_cache[maintainer] = maintainer_id
+    cursor.close()
+
+    return maintainer_id
+
+
+def __get_changedby__(package, version):
+    cur = sqliteConn.cursor()
+    cur.execute("SELECT changedby FROM uploads WHERE package=? AND version=? LIMIT 1", (package, version))
+    res = cur.fetchone()
+    cur.close()
+    return res
+
+def insert ():
+    print "Adding missing changedby fields."
+
+    listcursor = projectBdb.cursor()
+    listcursor.execute("SELECT id, source, version FROM source WHERE changedby IS NULL")
+    row = listcursor.fetchone()
+
+    while row:
+        print repr(row)
+        try:
+            res = __get_changedby__(row[1], row[2])
+        except:
+            sqliteConn.text_factory = str
+            try:
+                res = __get_changedby__(row[1], row[2])
+            except:
+                print 'FAILED SQLITE'
+                res=None
+            sqliteConn.text_factory = unicode
+        if res:
+            changedby_id = get_or_set_maintainer_id(res[0])
+
+            cur = projectBdb.cursor()
+            cur.execute("UPDATE source SET changedby=%s WHERE id=%s" % (changedby_id, row[0]))
+            cur.close()
+            print changedby_id, "(%d)" % row[0]
+
+        else:
+            print "nothing found"
+
+        row = listcursor.fetchone()
+    listcursor.close()
+
+###############################################################################
+
+
+def main():
+    global projectB, sqliteConn, projectBdb
+
+    Cnf = daklib.utils.get_conf()
+    Upload = daklib.queue.Upload(Cnf)
+    projectB = Upload.projectB
+    projectBdb = psycopg2.connect("dbname=%s" % Cnf["DB::Name"])
+
+    sqliteConn = sqlite.connect(DBNAME)
+
+    insert()
+
+    projectBdb.commit()
+    projectBdb.close()
+
+###############################################################################
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/debian/mkchecksums b/scripts/debian/mkchecksums
new file mode 100755 (executable)
index 0000000..575d55c
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# Update the md5sums file
+
+set -e
+. $SCRIPTVARS
+
+dsynclist=$dbdir/dsync.list
+md5list=$indices/md5sums
+
+echo -n "Creating md5 / dsync index file ... "
+
+cd "$ftpdir"
+dsync-flist -q generate $dsynclist --exclude $dsynclist --md5
+dsync-flist -q md5sums $dsynclist | gzip -9n > ${md5list}.gz
+dsync-flist -q link-dups $dsynclist || true
diff --git a/scripts/debian/mkfilesindices b/scripts/debian/mkfilesindices
new file mode 100755 (executable)
index 0000000..03b3ea0
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh -e
+
+export SCRIPTVARS=/srv/ftp.debian.org/dak/config/debian/vars
+. $SCRIPTVARS
+umask 002
+
+cd $base/ftp/indices/files/components
+
+ARCHLIST=$(tempfile)
+
+echo "Querying projectb..."
+
+echo 'SELECT l.path, f.filename, a.arch_string FROM location l JOIN files f ON (f.location = l.id) LEFT OUTER JOIN (binaries b JOIN architecture a ON (b.architecture = a.id)) ON (f.id = b.file)' | psql projectb -At | sed 's/|//;s,^/srv/ftp.debian.org/ftp,.,' | sort >$ARCHLIST
+
+includedirs () {
+    perl -ne 'print; while (m,/[^/]+$,) { $_=$`; print $_ . "\n" unless $d{$_}++; }'
+}
+
+poolfirst () {
+    perl -e '@nonpool=(); while (<>) { if (m,^\./pool/,) { print; } else { push @nonpool, $_; } } print for (@nonpool);'
+}
+
+echo "Generating sources list..."
+
+(
+  sed -n 's/|$//p' $ARCHLIST
+  cd $base/ftp
+  find ./dists -maxdepth 1 \! -type d
+  find ./dists \! -type d | grep "/source/"
+) | sort -u | gzip -9 > source.list.gz
+
+echo "Generating arch lists..."
+
+ARCHES=$( (<$ARCHLIST sed -n 's/^.*|//p'; echo amd64) | grep . | grep -v all | sort -u)
+for a in $ARCHES; do
+  (sed -n "s/|$a$//p" $ARCHLIST
+   sed -n 's/|all$//p' $ARCHLIST
+
+   cd $base/ftp
+   find ./dists -maxdepth 1 \! -type d
+   find ./dists \! -type d | grep -E "(proposed-updates.*_$a.changes$|/main/disks-$a/|/main/installer-$a/|/Contents-$a|/binary-$a/)"
+  ) | sort -u | gzip -9 > arch-$a.list.gz
+done
+
+echo "Generating suite lists..."
+
+suite_list () {
+    printf 'SELECT DISTINCT l.path, f.filename FROM (SELECT sa.source AS source FROM src_associations sa WHERE sa.suite = %d UNION SELECT b.source AS source FROM bin_associations ba JOIN binaries b ON (ba.bin = b.id) WHERE ba.suite = %d) s JOIN dsc_files df ON (s.source = df.source) JOIN files f ON (df.file = f.id) JOIN location l ON (f.location = l.id)\n' $1 $1 | psql -F' ' -A -t projectb
+
+    printf 'SELECT l.path, f.filename FROM bin_associations ba JOIN binaries b ON (ba.bin = b.id) JOIN files f ON (b.file = f.id) JOIN location l ON (f.location = l.id) WHERE ba.suite = %d\n' $1 | psql -F' ' -A -t projectb
+}
+
+printf 'SELECT id, suite_name FROM suite\n' | psql -F' ' -At projectb |
+  while read id suite; do
+    [ -e $base/ftp/dists/$suite ] || continue
+    (
+     (cd $base/ftp
+      distname=$(cd dists; readlink $suite || echo $suite)
+      find ./dists/$distname \! -type d
+      for distdir in ./dists/*; do 
+        [ "$(readlink $distdir)" != "$distname" ] || echo $distdir
+      done
+     )
+     suite_list $id | tr -d ' ' | sed 's,^/srv/ftp.debian.org/ftp,.,'
+    ) | sort -u | gzip -9 > suite-${suite}.list.gz
+  done
+
+echo "Finding everything on the ftp site to generate sundries $(date +"%X")..."
+
+(cd $base/ftp; find . \! -type d \! -name 'Archive_Maintenance_In_Progress' | sort) >$ARCHLIST
+
+rm -f sundries.list
+zcat *.list.gz | cat - *.list | sort -u | 
+  diff - $ARCHLIST | sed -n 's/^> //p' > sundries.list
+
+echo "Generating files list $(date +"%X")..."
+
+for a in $ARCHES; do
+  (echo ./project/trace; zcat arch-$a.list.gz source.list.gz) | 
+    cat - sundries.list dists.list project.list docs.list indices.list |
+    sort -u | poolfirst > ../arch-$a.files
+done
+
+(cd $base/ftp/
+       for dist in sid lenny; do
+               find ./dists/$dist/main/i18n/ \! -type d | sort -u | gzip -9 > $base/ftp/indices/files/components/translation-$dist.list.gz
+       done
+)
+
+(cat ../arch-i386.files ../arch-amd64.files; zcat suite-proposed-updates.list.gz ; zcat translation-sid.list.gz ; zcat translation-lenny.list.gz) |
+   sort -u | poolfirst > ../typical.files
+
+rm -f $ARCHLIST
+
+echo "Done!"
diff --git a/scripts/debian/mklslar b/scripts/debian/mklslar
new file mode 100755 (executable)
index 0000000..19363f1
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+# Update the ls-lR.
+
+set -e
+. $SCRIPTVARS
+
+cd $ftpdir
+
+filename=ls-lR
+
+echo "Removing any core files ..."
+find -type f -name core -print0 | xargs -0r rm -v
+
+echo "Checking permissions on files in the FTP tree ..."
+find -type f \( \! -perm -444 -o -perm +002 \) -ls
+find -type d \( \! -perm -555 -o -perm +002 \) -ls
+
+echo "Checking symlinks ..."
+symlinks -rd .
+
+echo "Creating recursive directory listing ... "
+rm -f .$filename.new
+TZ=UTC ls -lR | grep -v Archive_Maintenance_In_Progress > .$filename.new
+
+if [ -r ${filename}.gz ] ; then
+  mv -f ${filename}.gz $filename.old.gz
+  mv -f .$filename.new $filename
+  rm -f $filename.patch.gz
+  zcat $filename.old.gz | diff -u - $filename | gzip -9cfn - >$filename.patch.gz
+  rm -f $filename.old.gz
+else
+  mv -f .$filename.new $filename
+fi
+
+gzip -9cfN $filename >$filename.gz
+rm -f $filename
diff --git a/scripts/debian/mkmaintainers b/scripts/debian/mkmaintainers
new file mode 100755 (executable)
index 0000000..a0abaa1
--- /dev/null
@@ -0,0 +1,28 @@
+#! /bin/sh
+
+echo
+echo -n 'Creating Maintainers index ... '
+
+set -e
+. $SCRIPTVARS
+cd $base/misc/
+
+cd $indices
+dak make-maintainers ${scriptdir}/masterfiles/pseudo-packages.maintainers | sed -e "s/~[^  ]*\([   ]\)/\1/"  | awk '{printf "%-20s ", $1; for (i=2; i<=NF; i++) printf "%s ", $i; printf "\n";}' > .new-maintainers
+
+set +e
+cmp .new-maintainers Maintainers >/dev/null
+rc=$?
+set -e
+if [ $rc = 1 ] || [ ! -f Maintainers ] ; then
+       echo -n "installing Maintainers ... "
+       mv -f .new-maintainers Maintainers
+       gzip -9v <Maintainers >.new-maintainers.gz
+       mv -f .new-maintainers.gz Maintainers.gz
+elif [ $rc = 0 ] ; then
+       echo '(same as before)'
+       rm -f .new-maintainers
+else
+       echo cmp returned $rc
+       false
+fi
diff --git a/scripts/debian/stats.R b/scripts/debian/stats.R
new file mode 100644 (file)
index 0000000..df12847
--- /dev/null
@@ -0,0 +1,6 @@
+x <- read.table("x.1",row.names=1,col.names=c("Packages", "Sizes"))
+y <- t(x)
+postscript(file="x4.png")
+barplot(y, beside=TRUE, col = c("red", "green"), main="Daily dinstall run size", legend = colnames(x), xlab="Date", ylab="Packages/Size (Mb)")
+axis(4)
+dev.off()
diff --git a/scripts/debian/trigger.daily b/scripts/debian/trigger.daily
new file mode 100755 (executable)
index 0000000..0107564
--- /dev/null
@@ -0,0 +1,196 @@
+#!/bin/bash
+#
+# Updates wanna-build databases after the archive maintenance
+# finishes
+#
+# Files:
+#     Sources-* == upstream fetched file
+#     Sources.* == uncompressed, concat'd version
+PATH="/bin:/usr/bin"
+#testing must be before unstable so late upld don't build for testing needlessly
+DISTS="oldstable-security stable-security testing-security stable testing unstable"
+STATS_DISTS="unstable testing stable"
+SECTIONS="main contrib non-free"
+ARCHS_oldstable="m68k arm sparc alpha powerpc i386 mips mipsel ia64 hppa s390"
+ARCHS_stable="$ARCHS_oldstable"
+ARCHS_testing="$ARCHS_stable"
+ARCHS_unstable="$ARCHS_testing hurd-i386"
+TMPDIR="/org/wanna-build/tmp"
+WGETOPT="-q -t2 -w0 -T10"
+CURLOPT="-q -s -S -f -y 5 -K /org/wanna-build/trigger.curlrc"
+LOCKFILE="/org/wanna-build/tmp/DB_Maintenance_In_Progress"
+
+DAY=`date +%w`
+
+if lockfile -! -l 3600 $LOCKFILE; then
+       echo "Cannot lock $LOCKFILE"
+       exit 1
+fi
+
+cleanup() {
+       rm -f "$LOCKFILE"
+}
+trap cleanup 0
+
+echo Updating wanna-build databases...
+umask 027
+
+if [ "$DAY" = "0" ]; then
+       savelog -c 26 -p /org/wanna-build/db/merge.log
+fi
+
+exec >> /org/wanna-build/db/merge.log 2>&1
+
+echo -------------------------------------------------------------------------
+echo "merge triggered `date`"
+
+cd $TMPDIR
+
+#
+# Make one big Packages and Sources file.
+#
+for d in $DISTS ; do
+       dist=`echo $d | sed s/-.*$//`
+       case "$dist" in
+               oldstable)
+                       ARCHS="$ARCHS_oldstable"
+                       ;;
+               stable)
+                       ARCHS="$ARCHS_stable"
+                       ;;
+               testing)
+                       ARCHS="$ARCHS_testing"
+                       ;;
+               *)
+                       ARCHS="$ARCHS_unstable"
+                       ;;
+       esac
+       rm -f Sources.$d
+       if [ "$d" = "unstable" ]; then
+               gzip -dc /org/incoming.debian.org/buildd/Sources.gz >> Sources.$d
+       fi
+       for a in $ARCHS ; do
+               rm -f Packages.$d.$a quinn-$d.$a
+               if [ "$d" = "unstable" ]; then
+                       gzip -dc /org/incoming.debian.org/buildd/Packages.gz >> Packages.$d.$a
+               fi
+       done
+
+       for s in $SECTIONS ; do
+               if echo $d | grep -qv -- -security; then
+                       rm -f Sources.gz
+                       gzip -dc /org/ftp.debian.org/ftp/dists/$d/$s/source/Sources.gz >> Sources.$d
+                       if [ "$d" = "testing" -o "$d" = "stable" ]; then
+                               gzip -dc /org/ftp.debian.org/ftp/dists/$d-proposed-updates/$s/source/Sources.gz >> Sources.$d
+                       fi
+
+                       rm -f Packages.gz
+                       for a in $ARCHS ; do
+                               gzip -dc /org/ftp.debian.org/ftp/dists/$d/$s/binary-$a/Packages.gz >> Packages.$d.$a
+                               if [ "$d" = "testing" -o "$d" = "stable" ]; then
+                                       gzip -dc /org/ftp.debian.org/ftp/dists/$d-proposed-updates/$s/binary-$a/Packages.gz >> Packages.$d.$a
+                               fi
+                               if [ "$d" = "unstable" -a "$s" = "main" ]; then
+                                       gzip -dc /org/ftp.debian.org/ftp/dists/$d/$s/debian-installer/binary-$a/Packages.gz >> Packages.$d.$a
+                               fi
+                       done
+               else
+                       rm -f Sources.gz
+                       if wget $WGETOPT http://security.debian.org/debian-security/dists/$dist/updates/$s/source/Sources.gz; then
+                               mv Sources.gz Sources-$d.$s.gz
+                       fi
+                       gzip -dc Sources-$d.$s.gz >> Sources.$d
+                       if [ "$s" = "main" ]; then
+                               if curl $CURLOPT http://security.debian.org/buildd/$dist/Sources.gz -o Sources.gz; then
+                                       mv Sources.gz Sources-$d.accepted.gz
+                               fi
+                               gzip -dc Sources-$d.accepted.gz >> Sources.$d
+                               if curl $CURLOPT http://security.debian.org/buildd/$dist/Packages.gz -o Packages.gz; then
+                                       mv Packages.gz Packages.$d.accepted.gz
+                               fi
+                       fi
+                       rm -f Packages.gz
+                       for a in $ARCHS ; do
+                               if wget $WGETOPT http://security.debian.org/debian-security/dists/$dist/updates/$s/binary-$a/Packages.gz; then
+                                       mv Packages.gz Packages.$d.$s.$a.gz
+                               fi
+                               gzip -dc Packages.$d.$s.$a.gz >> Packages.$d.$a
+                               if [ "$s" = "main" ]; then
+                                       gzip -dc Packages.$d.accepted.gz >> Packages.$d.$a
+                               fi
+                       done
+               fi
+       done
+
+       for a in $ARCHS ; do
+               if [ "$d" = "unstable" -o ! -e "quinn-unstable.$a-old" ]; then
+                       quinn-diff -A $a -a /org/buildd.debian.org/web/quinn-diff/Packages-arch-specific -s Sources.$d -p Packages.$d.$a >> quinn-$d.$a
+               else
+                       if echo $d | grep -qv -- -security; then
+                               quinn-diff -A $a -a /org/buildd.debian.org/web/quinn-diff/Packages-arch-specific -s Sources.$d -p Packages.$d.$a | fgrep -v -f quinn-unstable.$a-old | grep ":out-of-date\]$" >> quinn-$d.$a
+                               sed -e 's/\[\w*:\w*]$//' quinn-$d-security.$a > quinn-$d-security.$a.grep
+                               grep -vf quinn-$d-security.$a quinn-$d.$a > quinn-$d.$a.grep
+                               mv quinn-$d.$a.grep quinn-$d.$a
+                               rm quinn-$d-security.$a.grep
+                       else
+                               quinn-diff -A $a -a /org/buildd.debian.org/web/quinn-diff/Packages-arch-specific -s Sources.$d -p Packages.$d.$a >> quinn-$d.$a
+                       fi
+               fi
+       done
+done
+
+umask 002
+for a in $ARCHS_unstable ; do
+       wanna-build --create-maintenance-lock --database=$a/build-db
+
+       for d in $DISTS ; do
+               dist=`echo $d | sed s/-.*$//`
+               case "$dist" in
+                       oldstable)
+                               if echo $ARCHS_oldstable | grep -q -v "\b$a\b"; then
+                                       continue
+                               fi
+                               ;;
+                       stable)
+                               if echo $ARCHS_stable | grep -q -v "\b$a\b"; then
+                                       continue
+                               fi
+                               ;;
+                       testing)
+                               if echo $ARCHS_testing | grep -q -v "\b$a\b"; then
+                                       continue
+                               fi
+                               ;;
+                       *)
+                               if echo $ARCHS_unstable | grep -q -v "\b$a\b"; then
+                                       continue
+                               fi
+                               ;;
+               esac
+               perl -pi -e 's#^(non-free)/.*$##msg' quinn-$d.$a
+               wanna-build --merge-all --arch=$a --dist=$d --database=$a/build-db Packages.$d.$a quinn-$d.$a Sources.$d
+               mv Packages.$d.$a Packages.$d.$a-old
+               mv quinn-$d.$a quinn-$d.$a-old
+       done
+       if [ "$DAY" = "0" ]; then
+               savelog -p -c 26 /org/wanna-build/db/$a/transactions.log
+       fi
+       wanna-build --remove-maintenance-lock --database=$a/build-db
+done
+umask 022
+for d in $DISTS; do
+       mv Sources.$d Sources.$d-old
+done
+
+echo "merge ended `date`"
+/org/wanna-build/bin/wb-graph >> /org/wanna-build/etc/graph-data
+/org/wanna-build/bin/wb-graph -p >> /org/wanna-build/etc/graph2-data
+rm -f "$LOCKFILE"
+trap -
+/org/buildd.debian.org/bin/makegraph
+for a in $ARCHS_stable; do
+       echo Last Updated: `date -u` > /org/buildd.debian.org/web/stats/$a.txt
+       for d in $STATS_DISTS; do
+               /org/wanna-build/bin/wanna-build-statistics --database=$a/build-db --dist=$d >> /org/buildd.debian.org/web/stats/$a.txt
+       done
+done
diff --git a/scripts/debian/update-bugdoctxt b/scripts/debian/update-bugdoctxt
new file mode 100755 (executable)
index 0000000..d5568f5
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+
+. vars
+
+export TERM=linux
+
+destdir=$ftpdir/doc
+urlbase=http://www.debian.org/Bugs/
+
+cd $destdir
+
+convert () {
+       src=$1; dst=$2
+       rm -f .new-$dst
+       echo Generating $dst from http://www.debian.org/Bugs/$src ...
+       lynx -nolist -dump $urlbase$src | sed -e 's/^ *$//' | perl -00 -ne 'exit if /Back to the Debian Project homepage/; print unless ($.==1 || $.==2 || $.==3 || /^\s*Other BTS pages:$/m)' >.new-$dst
+       if cmp -s .new-$dst $dst ; then rm -f .new-$dst
+       else mv -f .new-$dst $dst
+       fi
+}
+
+convert Reporting.html bug-reporting.txt
+convert Access.html bug-log-access.txt
+convert server-request.html bug-log-mailserver.txt
+convert Developer.html bug-maint-info.txt
+convert server-control.html bug-maint-mailcontrol.txt
+convert server-refcard.html bug-mailserver-refcard.txt
diff --git a/scripts/debian/update-ftpstats b/scripts/debian/update-ftpstats
new file mode 100755 (executable)
index 0000000..b9febef
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+my %data;
+my %data2;
+my @archs = ("source", "all", "amd64", "i386", "alpha", "arm", "armel", "hppa",
+       "hurd-i386", "ia64", "mips", "mipsel", "powerpc", "s390",
+       "sparc");
+
+while (<>) {
+       if (/^(\d{8})\d{6}\|(?:k(?:atie|elly)|process-accepted)\|installed\|[^|]+\|[^|]+\|(\d+)\|([-\w]+)$/) {
+               if (not defined $data{$1}) {
+                       foreach $a (@archs) {
+                               $data{$1}{$a} = 0;
+                       }
+               }
+               $data{$1}{$3} += $2;
+               $data2{$1}{$3}++;
+       }
+}
+
+foreach $p (sort keys %data) {
+       print "$p";
+       foreach $a (@archs) {
+               print ", $data{$p}{$a}";
+       }
+       print "\n";
+}
diff --git a/scripts/debian/update-mailingliststxt b/scripts/debian/update-mailingliststxt
new file mode 100755 (executable)
index 0000000..3b67eb9
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# Fetches latest copy of mailing-lists.txt
+# Michael Beattie <mjb@debian.org>
+
+. vars
+
+cd $ftpdir/doc
+
+echo Updating archive version of mailing-lists.txt
+wget -t1 -T20 -q -N http://www.debian.org/misc/mailing-lists.txt || \
+  echo "Some error occured..."
+
diff --git a/scripts/debian/update-mirrorlists b/scripts/debian/update-mirrorlists
new file mode 100755 (executable)
index 0000000..864fbb3
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# Very Very hackish script...  dont laugh.
+# Michael Beattie <mjb@debian.org>
+
+. vars
+
+prog=$scriptdir/mirrorlist/mirror_list.pl
+masterlist=$scriptdir/mirrorlist/Mirrors.masterlist
+
+test ! -f $HOME/.cvspass && \
+       echo ":pserver:anonymous@cvs.debian.org:/cvs/webwml A" > $HOME/.cvspass
+grep -q "cvs.debian.org:/cvs/webwml" ~/.cvspass || \
+       echo ":pserver:anonymous@cvs.debian.org:/cvs/webwml A" >> $HOME/.cvspass
+
+cd $(dirname $masterlist)
+cvs update
+
+if [ ! -f $ftpdir/README.mirrors.html -o $masterlist -nt $ftpdir/README.mirrors.html ] ; then
+       rm -f $ftpdir/README.mirrors.html $ftpdir/README.mirrors.txt
+       $prog -m $masterlist -t html > $ftpdir/README.mirrors.html
+       $prog -m $masterlist -t text > $ftpdir/README.mirrors.txt
+       echo Updated archive version of mirrors file
+fi
diff --git a/scripts/debian/update-pseudopackages.sh b/scripts/debian/update-pseudopackages.sh
new file mode 100755 (executable)
index 0000000..d9a4d23
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+#
+# Fetches latest copy of pseudo-packages
+# Joerg Jaspert <joerg@debian.org>
+
+. vars
+
+cd ${scriptdir}/masterfiles
+
+echo Updating archive version of pseudo-packages
+for file in maintainers description; do
+       wget -t2 -T20 -q -N http://bugs.debian.org/pseudopackages/pseudo-packages.${file} || echo "Some error occured with $file..."
+done
diff --git a/scripts/nfu/get-w-b-db b/scripts/nfu/get-w-b-db
new file mode 100755 (executable)
index 0000000..84c15e2
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+set -e
+
+# list of architectures taken from
+# http://buildd.debian.org/stats/
+
+# For debugging, you can override the path using
+# the WB_DB_DIR enviroment variable
+if [ -z "$WB_DB_DIR" ]
+then
+       WB_DB_DIR=/srv/ftp.debian.org/scripts/nfu
+fi
+
+cd $WB_DB_DIR || { echo "Failed to cd to $WB_DB_DIR" ; exit 1 ;}
+
+for arch in alpha amd64 arm armel hppa i386 ia64 mips mipsel powerpc s390 sparc
+do
+       rm -f $arch-all.txt
+       echo "Getting $arch-all.txt"
+       wget -q http://buildd.debian.org/stats/$arch-all.txt
+done
diff --git a/setup/add_constraints.sql b/setup/add_constraints.sql
new file mode 100644 (file)
index 0000000..1d2bad6
--- /dev/null
@@ -0,0 +1,129 @@
+-- Fix up after population of the database...
+
+-- First of all readd the constraints (takes ~1:30 on auric)
+
+ALTER TABLE files ADD CONSTRAINT files_location FOREIGN KEY (location) REFERENCES location(id) MATCH FULL;
+
+ALTER TABLE source ADD CONSTRAINT source_maintainer FOREIGN KEY (maintainer) REFERENCES maintainer(id) MATCH FULL;
+ALTER TABLE source ADD CONSTRAINT source_changedby FOREIGN KEY (changedby) REFERENCES maintainer(id) MATCH FULL;
+ALTER TABLE source ADD CONSTRAINT source_file FOREIGN KEY (file) REFERENCES files(id) MATCH FULL;
+ALTER TABLE source ADD CONSTRAINT source_sig_fpr FOREIGN KEY (sig_fpr) REFERENCES fingerprint(id) MATCH FULL;
+
+ALTER TABLE dsc_files ADD CONSTRAINT dsc_files_source FOREIGN KEY (source) REFERENCES source(id) MATCH FULL;
+ALTER TABLE dsc_files ADD CONSTRAINT dsc_files_file FOREIGN KEY (file) REFERENCES files(id) MATCH FULL;
+
+ALTER TABLE binaries ADD CONSTRAINT binaries_maintainer FOREIGN KEY (maintainer) REFERENCES maintainer(id) MATCH FULL;
+ALTER TABLE binaries ADD CONSTRAINT binaries_source FOREIGN KEY (source) REFERENCES source(id) MATCH FULL;
+ALTER TABLE binaries ADD CONSTRAINT binaries_architecture FOREIGN KEY (architecture) REFERENCES architecture(id) MATCH FULL;
+ALTER TABLE binaries ADD CONSTRAINT binaries_file FOREIGN KEY (file) REFERENCES files(id) MATCH FULL;
+ALTER TABLE binaries ADD CONSTRAINT binaries_sig_fpr FOREIGN KEY (sig_fpr) REFERENCES fingerprint(id) MATCH FULL;
+
+ALTER TABLE suite_architectures ADD CONSTRAINT suite_architectures_suite FOREIGN KEY (suite) REFERENCES suite(id) MATCH FULL;
+ALTER TABLE suite_architectures ADD CONSTRAINT suite_architectures_architecture FOREIGN KEY (architecture) REFERENCES architecture(id) MATCH FULL;
+
+ALTER TABLE bin_associations ADD CONSTRAINT bin_associations_suite FOREIGN KEY (suite) REFERENCES suite(id) MATCH FULL;
+ALTER TABLE bin_associations ADD CONSTRAINT bin_associations_bin FOREIGN KEY (bin) REFERENCES binaries(id) MATCH FULL;
+
+ALTER TABLE src_associations ADD CONSTRAINT src_associations_suite FOREIGN KEY (suite) REFERENCES suite(id) MATCH FULL;
+ALTER TABLE src_associations ADD CONSTRAINT src_associations_source FOREIGN KEY (source) REFERENCES source(id) MATCH FULL;
+
+ALTER TABLE override ADD CONSTRAINT override_suite FOREIGN KEY (suite) REFERENCES suite(id) MATCH FULL;
+ALTER TABLE override ADD CONSTRAINT override_component FOREIGN KEY (component) REFERENCES component(id) MATCH FULL;
+ALTER TABLE override ADD CONSTRAINT override_priority FOREIGN KEY (priority) REFERENCES priority(id) MATCH FULL;
+ALTER TABLE override ADD CONSTRAINT override_section FOREIGN KEY (section) REFERENCES section(id) MATCH FULL;
+ALTER TABLE override ADD CONSTRAINT override_type FOREIGN KEY (type) REFERENCES override_type(id) MATCH FULL;
+
+ALTER TABLE queue_build ADD CONSTRAINT queue_build_suite FOREIGN KEY (suite) REFERENCES suite(id) MATCH FULL;
+ALTER TABLE queue_build ADD CONSTRAINT queue_build_queue FOREIGN KEY (queue) REFERENCES queue(id) MATCH FULL;
+
+-- Then correct all the id SERIAL PRIMARY KEY columns...
+
+CREATE FUNCTION files_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM files'
+    LANGUAGE 'sql';
+CREATE FUNCTION source_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM source'
+    LANGUAGE 'sql';
+CREATE FUNCTION src_associations_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM src_associations'
+    LANGUAGE 'sql';
+CREATE FUNCTION dsc_files_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM dsc_files'
+    LANGUAGE 'sql';
+CREATE FUNCTION binaries_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM binaries'
+    LANGUAGE 'sql';
+CREATE FUNCTION bin_associations_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM bin_associations'
+    LANGUAGE 'sql';
+CREATE FUNCTION section_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM section'
+    LANGUAGE 'sql';
+CREATE FUNCTION priority_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM priority'
+    LANGUAGE 'sql';
+CREATE FUNCTION override_type_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM override_type'
+    LANGUAGE 'sql';
+CREATE FUNCTION maintainer_id_max() RETURNS INT4
+    AS 'SELECT max(id) FROM maintainer'
+    LANGUAGE 'sql';
+
+SELECT setval('files_id_seq', files_id_max());
+SELECT setval('source_id_seq', source_id_max());
+SELECT setval('src_associations_id_seq', src_associations_id_max());
+SELECT setval('dsc_files_id_seq', dsc_files_id_max());
+SELECT setval('binaries_id_seq', binaries_id_max());
+SELECT setval('bin_associations_id_seq', bin_associations_id_max());
+SELECT setval('section_id_seq', section_id_max());
+SELECT setval('priority_id_seq', priority_id_max());
+SELECT setval('override_type_id_seq', override_type_id_max());
+SELECT setval('maintainer_id_seq', maintainer_id_max());
+
+-- Vacuum the tables for efficency
+
+VACUUM archive;
+VACUUM component;
+VACUUM architecture;
+VACUUM maintainer;
+VACUUM location;
+VACUUM files;
+VACUUM source;
+VACUUM dsc_files;
+VACUUM binaries;
+VACUUM suite;
+VACUUM suite_architectures;
+VACUUM bin_associations;
+VACUUM src_associations;
+VACUUM section;
+VACUUM priority;
+VACUUM override_type;
+VACUUM override;
+
+-- FIXME: has to be a better way to do this
+GRANT ALL ON architecture, architecture_id_seq, archive,
+  archive_id_seq, bin_associations, bin_associations_id_seq, binaries,
+  binaries_id_seq, component, component_id_seq, dsc_files,
+  dsc_files_id_seq, files, files_id_seq, fingerprint,
+  fingerprint_id_seq, keyrings, keyrings_id_seq,
+  location, location_id_seq, maintainer,
+  maintainer_id_seq, override, override_type, override_type_id_seq,
+  priority, priority_id_seq, section, section_id_seq, source,
+  source_id_seq, src_uploaders, src_uploaders_id_seq,
+  src_associations, src_associations_id_seq, suite,
+  suite_architectures, suite_id_seq, queue_build, uid,
+  uid_id_seq TO GROUP ftpmaster;
+
+-- Read only access to user 'nobody'
+GRANT SELECT ON architecture, architecture_id_seq, archive,
+  archive_id_seq, bin_associations, bin_associations_id_seq, binaries,
+  binaries_id_seq, component, component_id_seq, dsc_files,
+  dsc_files_id_seq, files, files_id_seq, fingerprint,
+  fingerprint_id_seq, keyrings, keyrings_id_seq,
+  location, location_id_seq, maintainer,
+  maintainer_id_seq, override, override_type, override_type_id_seq,
+  priority, priority_id_seq, section, section_id_seq, source,
+  source_id_seq, src_uploaders, src_uploaders_id_seq,
+  src_associations, src_associations_id_seq, suite,
+  suite_architectures, suite_id_seq, queue_build, uid,
+  uid_id_seq TO PUBLIC;
diff --git a/setup/init_pool.sql b/setup/init_pool.sql
new file mode 100644 (file)
index 0000000..1e36394
--- /dev/null
@@ -0,0 +1,211 @@
+DROP DATABASE projectb;
+CREATE DATABASE projectb WITH ENCODING = 'SQL_ASCII';
+
+\c projectb
+
+CREATE TABLE archive (
+       id SERIAL PRIMARY KEY,
+       name TEXT UNIQUE NOT NULL,
+       origin_server TEXT,
+       description TEXT
+);
+
+CREATE TABLE component (
+       id SERIAL PRIMARY KEY,
+       name TEXT UNIQUE NOT NULL,
+       description TEXT,
+       meets_dfsg BOOLEAN
+);
+
+CREATE TABLE architecture (
+       id SERIAL PRIMARY KEY,
+       arch_string TEXT UNIQUE NOT NULL,
+       description TEXT
+);
+
+CREATE TABLE maintainer (
+       id SERIAL PRIMARY KEY,
+       name TEXT UNIQUE NOT NULL
+);
+
+CREATE TABLE src_uploaders (
+       id SERIAL PRIMARY KEY,
+       source INT4 NOT NULL REFERENCES source,
+       maintainer INT4 NOT NULL REFERENCES maintainer
+);
+
+CREATE TABLE uid (
+       id SERIAL PRIMARY KEY,
+       uid TEXT UNIQUE NOT NULL,
+       name TEXT
+);
+
+CREATE TABLE keyrings (
+       id SERIAL PRIMARY KEY,
+       name TEXT
+);
+
+
+CREATE TABLE fingerprint (
+       id SERIAL PRIMARY KEY,
+       fingerprint TEXT UNIQUE NOT NULL,
+       uid INT4 REFERENCES uid,
+       keyring INT4 REFERENCES keyrings
+);
+
+CREATE TABLE location (
+       id SERIAL PRIMARY KEY,
+       path TEXT NOT NULL,
+       component INT4 REFERENCES component,
+       archive INT4 REFERENCES archive,
+       type TEXT NOT NULL
+);
+
+-- No references below here to allow sane population; added post-population
+
+CREATE TABLE files (
+       id SERIAL PRIMARY KEY,
+       filename TEXT NOT NULL,
+       size INT8 NOT NULL,
+       md5sum TEXT NOT NULL,
+       location INT4 NOT NULL, -- REFERENCES location
+       last_used TIMESTAMP,
+       sha1sum TEXT NOT NULL,
+       sha256sum TEXT NOT NULL,
+       unique (filename, location)
+);
+
+CREATE TABLE source (
+        id SERIAL PRIMARY KEY,
+        source TEXT NOT NULL,
+        version TEXT NOT NULL,
+        maintainer INT4 NOT NULL, -- REFERENCES maintainer
+        changedby INT4 NOT NULL, -- REFERENCES maintainer
+        file INT4 UNIQUE NOT NULL, -- REFERENCES files
+       install_date TIMESTAMP NOT NULL,
+       sig_fpr INT4 NOT NULL, -- REFERENCES fingerprint
+       unique (source, version)
+);
+
+CREATE TABLE src_uploaders (
+       id SERIAL PRIMARY KEY,
+       source INT4 NOT NULL REFERENCES source,
+       maintainer INT4 NOT NULL REFERENCES maintainer
+);
+
+CREATE TABLE dsc_files (
+       id SERIAL PRIMARY KEY,
+       source INT4 NOT NULL, -- REFERENCES source,
+       file INT4 NOT NULL, -- RERENCES files
+       unique (source, file)
+);
+
+CREATE TABLE binaries (
+       id SERIAL PRIMARY KEY,
+       package TEXT NOT NULL,
+       version TEXT NOT NULL,
+       maintainer INT4 NOT NULL, -- REFERENCES maintainer
+       source INT4, -- REFERENCES source,
+       architecture INT4 NOT NULL, -- REFERENCES architecture
+       file INT4 UNIQUE NOT NULL, -- REFERENCES files,
+       type TEXT NOT NULL,
+-- joeyh@ doesn't want .udebs and .debs with the same name, which is why the unique () doesn't mention type
+       sig_fpr INT4 NOT NULL, -- REFERENCES fingerprint
+       unique (package, version, architecture)
+);
+
+CREATE TABLE suite (
+       id SERIAL PRIMARY KEY,
+       suite_name TEXT NOT NULL,
+       version TEXT,
+       origin TEXT,
+       label TEXT,
+       policy_engine TEXT,
+       description TEXT
+);
+
+CREATE TABLE queue (
+       id SERIAL PRIMARY KEY,
+       queue_name TEXT NOT NULL
+);
+
+CREATE TABLE suite_architectures (
+       suite INT4 NOT NULL, -- REFERENCES suite
+       architecture INT4 NOT NULL, -- REFERENCES architecture
+       unique (suite, architecture)
+);
+
+CREATE TABLE bin_associations (
+       id SERIAL PRIMARY KEY,
+       suite INT4 NOT NULL, -- REFERENCES suite
+       bin INT4 NOT NULL, -- REFERENCES binaries
+       unique (suite, bin)
+);
+
+CREATE TABLE src_associations (
+       id SERIAL PRIMARY KEY,
+       suite INT4 NOT NULL, -- REFERENCES suite
+       source INT4 NOT NULL, -- REFERENCES source
+       unique (suite, source)
+);
+
+CREATE TABLE section (
+       id SERIAL PRIMARY KEY,
+       section TEXT UNIQUE NOT NULL
+);
+
+CREATE TABLE priority (
+       id SERIAL PRIMARY KEY,
+       priority TEXT UNIQUE NOT NULL,
+       level INT4 UNIQUE NOT NULL
+);
+
+CREATE TABLE override_type (
+       id SERIAL PRIMARY KEY,
+       type TEXT UNIQUE NOT NULL
+);
+
+CREATE TABLE override (
+       package TEXT NOT NULL,
+       suite INT4 NOT NULL, -- references suite
+       component INT4 NOT NULL, -- references component
+       priority INT4, -- references priority
+       section INT4 NOT NULL, -- references section
+       type INT4 NOT NULL, -- references override_type
+       maintainer TEXT,
+       unique (suite, component, package, type)
+);
+
+CREATE TABLE queue_build (
+       suite INT4 NOT NULL, -- references suite
+       queue INT4 NOT NULL, -- references queue
+       filename TEXT NOT NULL,
+       in_queue BOOLEAN NOT NULL,
+       last_used TIMESTAMP
+);
+
+-- Critical indexes
+
+CREATE INDEX bin_associations_bin ON bin_associations (bin);
+CREATE INDEX src_associations_source ON src_associations (source);
+CREATE INDEX source_maintainer ON source (maintainer);
+CREATE INDEX binaries_maintainer ON binaries (maintainer);
+CREATE INDEX binaries_fingerprint on binaries (sig_fpr);
+CREATE INDEX source_fingerprint on source (sig_fpr);
+CREATE INDEX dsc_files_file ON dsc_files (file);
+
+-- Own function
+CREATE FUNCTION space_concat(text, text) RETURNS text
+    AS $_$select case
+WHEN $2 is null or $2 = '' THEN $1
+WHEN $1 is null or $1 = '' THEN $2
+ELSE $1 || ' ' || $2
+END$_$
+    LANGUAGE sql;
+
+CREATE AGGREGATE space_separated_list (
+    BASETYPE = text,
+    SFUNC = space_concat,
+    STYPE = text,
+    INITCOND = ''
+);
diff --git a/setup/init_pool.sql-security b/setup/init_pool.sql-security
new file mode 100644 (file)
index 0000000..7e47d3c
--- /dev/null
@@ -0,0 +1,8 @@
+
+CREATE TABLE disembargo (
+       package TEXT NOT NULL,
+       version TEXT NOT NULL
+);
+
+GRANT ALL ON disembargo TO GROUP ftpmaster;
+GRANT SELECT ON disembargo TO PUBLIC;
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..b45b07d
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/make -f
+
+CXXFLAGS       = -I/usr/include/postgresql/ -I/usr/include/postgresql/server/ -fPIC -Wall
+CFLAGS         = -fPIC -Wall
+LDFLAGS                = -fPIC
+LIBS           = -lapt-pkg
+
+C++            = g++
+
+all: sql-aptvc.so
+
+sql-aptvc.o: sql-aptvc.cpp
+sql-aptvc.so: sql-aptvc.o
+       $(CC) $(LDFLAGS) $(LIBS) -shared -o $@ $<
+clean:
+       rm -f sql-aptvc.so sql-aptvc.o
+
diff --git a/src/sql-aptvc.cpp b/src/sql-aptvc.cpp
new file mode 100644 (file)
index 0000000..54ba9e9
--- /dev/null
@@ -0,0 +1,56 @@
+/* Wrapper round apt's version compare functions for PostgreSQL. */
+/* Copyright (C) 2001, James Troup <james@nocrew.org> */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+/* NB: do not try to use the VERSION-1 calling conventions for
+   C-Language functions; it works on i386 but segfaults the postgres
+   child backend on Sparc. */
+
+#include <apt-pkg/debversion.h>
+
+extern "C"
+{
+
+#include <postgres.h>
+
+  int versioncmp(text *A, text *B);
+
+  int
+  versioncmp (text *A, text *B)
+  {
+    int result, txt_size;
+    char *a, *b;
+
+    txt_size = VARSIZE(A)-VARHDRSZ;
+    a = (char *) palloc(txt_size+1);
+    memcpy(a, VARDATA(A), txt_size);
+    a[txt_size] = '\0';
+
+    txt_size = VARSIZE(B)-VARHDRSZ;
+    b = (char *) palloc(txt_size+1);
+    memcpy(b, VARDATA(B), txt_size);
+    b[txt_size] = '\0';
+
+    result = debVS.CmpVersion (a, b);
+
+    pfree (a);
+    pfree (b);
+
+    return (result);
+  }
+
+}
diff --git a/templates/README b/templates/README
new file mode 100644 (file)
index 0000000..4ad32d9
--- /dev/null
@@ -0,0 +1,36 @@
+Currently used substitutions in dak
+===================================
+
+Global
+------
+
+ o __ADMIN_ADDRESS__
+ o __BCC__ [*]
+ o __BUG_SERVER__
+ o __DISTRO__
+ o __DAK_ADDRESS__
+
+Per package
+-----------
+
+ o __ARCHITECTURE__
+ o __CHANGES_FILENAME__
+ o __FILE_CONTENTS__
+ o __MAINTAINER_ADDRESS__
+ o __MAINTAINER__
+ o __REJECT_MESSAGE__
+ o __SOURCE__
+ o __VERSION__
+
+Misc
+----
+
+ o __ANNOUNCE_LIST_ADDRESS__
+ o __BUG_NUMBER__
+ o __CONTROL_MESSAGE__
+ o __MANUAL_REJECT_MESSAGE__
+ o __SHORT_SUMMARY__
+ o __SUMMARY__
+ o __STABLE_WARNING__
+ o __SUITE__
+
diff --git a/templates/override.bug-close b/templates/override.bug-close
new file mode 100644 (file)
index 0000000..5865651
--- /dev/null
@@ -0,0 +1,26 @@
+From: __OVERRIDE_ADDRESS__
+To: __BUG_NUMBER__-close@__BUG_SERVER__
+__CC__ 
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Bug#__BUG_NUMBER__: fixed
+
+We believe that the bug you reported is now fixed; the following
+changes were made to the overrides...
+
+__SUMMARY__
+
+Thank you for reporting the bug, which will now be closed.  If you
+have further comments please address them to __BUG_NUMBER__@__BUG_SERVER__.
+
+This message was generated automatically; if you believe that there is
+a problem with it please contact the archive administrators by mailing
+__ADMIN_ADDRESS__.
+
+__DISTRO__ distribution maintenance software
+pp.
+__WHOAMI__ (the ftpmaster behind the curtain)
diff --git a/templates/process-accepted.install b/templates/process-accepted.install
new file mode 100644 (file)
index 0000000..454fe81
--- /dev/null
@@ -0,0 +1,16 @@
+From: __DAK_ADDRESS__
+To: __MAINTAINER_TO__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+Precedence: bulk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: __CHANGES_FILENAME__ INSTALLED__SUITE__
+
+__REJECT_MESSAGE__
+Installing:
+__SUMMARY__
+
+Thank you for your contribution to __DISTRO__.
diff --git a/templates/process-accepted.unaccept b/templates/process-accepted.unaccept
new file mode 100644 (file)
index 0000000..abb98db
--- /dev/null
@@ -0,0 +1,23 @@
+From: __REJECTOR_ADDRESS__
+To: __MAINTAINER_TO__
+__CC__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: __CHANGES_FILENAME__ UNACCEPT
+
+__REJECT_MESSAGE__
+
+===
+
+Despite being ACCEPTed, this package failed the database sanity checks
+at the time of install.  This should only happen rarely and in
+corner-cases (a binary upload of a package which has since been
+'dak rm'-d for example), so no code to do the necessary unaccept
+actions has been written.  These actions (e.g. bug reopening,
+announcement rescinding, etc.) will have to be done by hand.  Also,
+the files have been left in the accepted directory; please deal with
+them as well.
diff --git a/templates/process-new.bxa_notification b/templates/process-new.bxa_notification
new file mode 100644 (file)
index 0000000..8fafaa9
--- /dev/null
@@ -0,0 +1,70 @@
+From: Ben Collins <bxa@ftp-master.debian.org>
+X-Not-Really-To: crypt@bis.doc.gov, enc@nsa.gov, web_site@bis.doc.gov
+To: bxa@ftp-master.debian.org
+__BCC__
+X-Debian: DAK
+Precedence: junk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: TSU Notification - Addition to __DISTRO__ Source Code
+
+SUBMISSION TYPE: TSU
+SUBMITTED FOR: Software in the Public Interest (Debian)
+POINT OF CONTACT: Ben Collins
+PHONE and/or FAX: (804) 695-9730
+PRODUCT NAME/MODEL #: Debian Source Code
+ECCN: 5D002
+
+NOTIFICATION: http://ftp.debian.org/debian/
+
+Re: Unrestricted Encryption Source Code Notification
+Commodity: Addition to Debian Source Code
+
+Attn: "TSU Notification"
+U.S. Department of Commerce
+Bureau of Industry and Security
+Office of National Security and Technology Transfer Controls (NSTTC)
+14th Street and Pennsylvania Avenue, NW
+Room 2705
+Washington, D.C. 20230
+Fax: (202) 219-9179
+
+Attn: ENC Encryption Request Coordinator
+9800 Savage Road, Suite 6940
+Ft. Meade, MD 20755-6000
+
+Dear Sir/Madam,
+
+     Pursuant to paragraph (e)(1) of Part 740.13 of the U.S. Export
+Administration Regulations ("EAR", 15 CFR Part 730 et seq.), we are
+providing this written notification of the Internet location of the
+unrestricted, publicly available Source Code of a package being added
+to the Debian Source Code. Debian Source Code is a free operating
+system developed by a group of individuals, coordinated by the
+non-profit Software in the Public Interest.  This notification serves
+as a notification of an addition of new software to the Debian
+archive.  Previous notifications have covered the archive as a whole
+and other software added in the past.  This archive is updated from
+time to time, but its location is constant.  Therefore this
+notification serves as a one-time notification for subsequent updates
+that may occur in the future to the software covered by this
+notification.  Such updates may add or enhance cryptographic
+functionality of the Debian operating system.  The Internet location
+for the Debian Source Code is: http://ftp.debian.org/debian/
+
+This site is mirrored to a number of other sites located outside the
+United States.
+
+The following software is being added to the Debian archive:
+
+----------------------------------------------------------------------
+__BINARY_DESCRIPTIONS__
+----------------------------------------------------------------------
+
+If you have any questions, please email me at bxa@ftp-master.debian.org,
+or call me on (804) 695-9730.
+
+     Sincerely,
+        Ben Collins
+        Debian Developer
diff --git a/templates/process-new.prod b/templates/process-new.prod
new file mode 100644 (file)
index 0000000..4d24dc0
--- /dev/null
@@ -0,0 +1,12 @@
+From: __FROM_ADDRESS__
+To: __MAINTAINER_TO__
+__CC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Comments regarding __CHANGES_FILENAME__
+
+__PROD_MESSAGE__
+
diff --git a/templates/process-unchecked.accepted b/templates/process-unchecked.accepted
new file mode 100644 (file)
index 0000000..e726825
--- /dev/null
@@ -0,0 +1,16 @@
+From: __DAK_ADDRESS__
+To: __MAINTAINER_TO__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+Precedence: bulk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: __CHANGES_FILENAME__ ACCEPTED__SUITE__
+
+__REJECT_MESSAGE__
+Accepted:
+__SUMMARY__
+
+Thank you for your contribution to __DISTRO__.
diff --git a/templates/process-unchecked.announce b/templates/process-unchecked.announce
new file mode 100644 (file)
index 0000000..7297c91
--- /dev/null
@@ -0,0 +1,14 @@
+From: __MAINTAINER_FROM__
+To: __ANNOUNCE_LIST_ADDRESS__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Accepted __SOURCE__ __VERSION__ (__ARCHITECTURE__)
+
+__FILE_CONTENTS__
+
+Accepted:
+__SHORT_SUMMARY__
diff --git a/templates/process-unchecked.bug-close b/templates/process-unchecked.bug-close
new file mode 100644 (file)
index 0000000..8ab7317
--- /dev/null
@@ -0,0 +1,36 @@
+From: __MAINTAINER_FROM__
+To: __BUG_NUMBER__-close@__BUG_SERVER__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Bug#__BUG_NUMBER__: fixed in __SOURCE__ __VERSION__
+
+Source: __SOURCE__
+Source-Version: __VERSION__
+
+We believe that the bug you reported is fixed in the latest version of
+__SOURCE__, which is due to be installed in the __DISTRO__ FTP archive:
+
+__SHORT_SUMMARY__
+__STABLE_WARNING__
+
+A summary of the changes between this version and the previous one is
+attached.
+
+Thank you for reporting the bug, which will now be closed.  If you
+have further comments please address them to __BUG_NUMBER__@__BUG_SERVER__,
+and the maintainer will reopen the bug report if appropriate.
+
+__DISTRO__ distribution maintenance software
+pp.
+__MAINTAINER__ (supplier of updated __SOURCE__ package)
+
+(This message was generated automatically at their request; if you
+believe that there is a problem with it please contact the archive
+administrators by mailing __ADMIN_ADDRESS__)
+
+
+__FILE_CONTENTS__
diff --git a/templates/process-unchecked.bug-experimental-fixed b/templates/process-unchecked.bug-experimental-fixed
new file mode 100644 (file)
index 0000000..34c5ca5
--- /dev/null
@@ -0,0 +1,18 @@
+From: __MAINTAINER_FROM__
+To: control@__BUG_SERVER__
+Cc: __MAINTAINER_TO__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Fixed in upload of __SOURCE__ __VERSION__ to experimental
+
+__CONTROL_MESSAGE__
+quit
+
+This message was generated automatically in response to an
+upload to the experimental distribution.  The .changes file follows.
+
+__FILE_CONTENTS__
diff --git a/templates/process-unchecked.bug-nmu-fixed b/templates/process-unchecked.bug-nmu-fixed
new file mode 100644 (file)
index 0000000..45f6c73
--- /dev/null
@@ -0,0 +1,18 @@
+From: __MAINTAINER_FROM__
+To: control@__BUG_SERVER__
+Cc: __MAINTAINER_TO__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Fixed in NMU of __SOURCE__ __VERSION__
+
+__CONTROL_MESSAGE__
+quit
+
+This message was generated automatically in response to a
+non-maintainer upload.  The .changes file follows.
+
+__FILE_CONTENTS__
diff --git a/templates/process-unchecked.new b/templates/process-unchecked.new
new file mode 100644 (file)
index 0000000..6c3162f
--- /dev/null
@@ -0,0 +1,19 @@
+From: __DAK_ADDRESS__
+To: __MAINTAINER_TO__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+Precedence: bulk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: __CHANGES_FILENAME__ is NEW
+
+__SUMMARY__
+
+Your package contains new components which requires manual editing of
+the override file.  It is ok otherwise, so please be patient.  New
+packages are usually added to the override file about once a week.
+
+You may have gotten the distribution wrong.  You'll get warnings above
+if files already exist in other distributions.
diff --git a/templates/process-unchecked.override-disparity b/templates/process-unchecked.override-disparity
new file mode 100644 (file)
index 0000000..bafbd4f
--- /dev/null
@@ -0,0 +1,34 @@
+From: __DAK_ADDRESS__
+To: __MAINTAINER_TO__
+__BCC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+Precedence: junk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: __SOURCE__ override disparity
+
+There are disparities between your recently accepted upload and the
+override file for the following file(s):
+
+__SUMMARY__
+Either the package or the override file is incorrect.  If you think
+the override is correct and the package wrong please fix the package
+so that this disparity is fixed in the next upload.  If you feel the
+override is incorrect then please reply to this mail and explain why.
+Please INCLUDE the list of packages as seen above, or we won't be able
+to deal with your mail due to missing information.
+
+[NB: this is an automatically generated mail; if you replied to one
+like it before and have not received a response yet, please ignore
+this mail.  Your reply needs to be processed by a human and will be in
+due course, but until then the installer will send these automated
+mails; sorry.]
+
+--
+__DISTRO__ distribution maintenance software
+
+(This message was generated automatically; if you believe that there
+is a problem with it please contact the archive administrators by
+mailing __ADMIN_ADDRESS__)
diff --git a/templates/queue.rejected b/templates/queue.rejected
new file mode 100644 (file)
index 0000000..7b02786
--- /dev/null
@@ -0,0 +1,19 @@
+From: __REJECTOR_ADDRESS__
+To: __MAINTAINER_TO__
+__BCC__
+__CC__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+Precedence: bulk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: __CHANGES_FILENAME__ REJECTED
+
+__MANUAL_REJECT_MESSAGE__
+__REJECT_MESSAGE__
+
+===
+
+If you don't understand why your files were rejected, or if the
+override file requires editing, reply to this email.
diff --git a/templates/reject-proposed-updates.rejected b/templates/reject-proposed-updates.rejected
new file mode 100644 (file)
index 0000000..8b86cb5
--- /dev/null
@@ -0,0 +1,33 @@
+From: __DAK_ADDRESS__
+To: __MAINTAINER_TO__
+__CC__
+__BCC__
+Reply-To: __STABLE_MAIL__
+X-Debian: DAK
+X-Debian-Package: __SOURCE__
+Precedence: bulk
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: __CHANGES_FILENAME__ REJECTED from proposed-updates
+
+Your package was rejected by an ftp master on behalf of
+__STABLE_REJECTOR__, if you have any questions or
+comments regarding this rejection, please address them to 
+__STABLE_REJECTOR__ by replying to this mail.
+
+The reason given for rejection was:
+
+__MANUAL_REJECT_MESSAGE__
+
+Please see:
+
+   __MORE_INFO_URL__
+
+for more details.
+
+===
+
+Your rejected .changes files is in queue/REJECT/; the other files
+have been removed from proposed-updates and will be auto-cleaned as
+normal.
diff --git a/templates/rm.bug-close b/templates/rm.bug-close
new file mode 100644 (file)
index 0000000..353d3cc
--- /dev/null
@@ -0,0 +1,43 @@
+From: __RM_ADDRESS__
+To: __BUG_NUMBER__-close@__BUG_SERVER__
+__CC__ 
+__BCC__
+X-Debian: DAK
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Subject: Bug#__BUG_NUMBER__: fixed
+
+We believe that the bug you reported is now fixed; the following
+package(s) have been removed from __SUITE_LIST__:
+
+__SUMMARY__
+Note that the package(s) have simply been removed from the tag
+database and may (or may not) still be in the pool; this is not a bug.
+The package(s) will be physically removed automatically when no suite
+references them (and in the case of source, when no binary references
+it).  Please also remember that the changes have been done on the
+master archive (__MASTER_ARCHIVE__) and will not propagate to any
+mirrors (__PRIMARY_MIRROR__ included) until the next cron.daily run at the
+earliest.
+
+Packages are never removed from testing by hand.  Testing tracks
+unstable and will automatically remove packages which were removed
+from unstable when removing them from testing causes no dependency
+problems.
+
+Bugs which have been reported against this package are not automatically
+removed from the Bug Tracking System.  Please check all open bugs and
+close them or re-assign them to another package if the removed package
+was superseded by another one.
+
+Thank you for reporting the bug, which will now be closed.  If you
+have further comments please address them to __BUG_NUMBER__@__BUG_SERVER__.
+
+This message was generated automatically; if you believe that there is
+a problem with it please contact the archive administrators by mailing
+__ADMIN_ADDRESS__.
+
+__DISTRO__ distribution maintenance software
+pp.
+__WHOAMI__ (the ftpmaster behind the curtain)
diff --git a/templates/security-install.advisory b/templates/security-install.advisory
new file mode 100644 (file)
index 0000000..eea2e93
--- /dev/null
@@ -0,0 +1,79 @@
+From: __DAK_ADDRESS__
+To: __WHOAMI__ <dak@security.debian.org>
+__BCC__
+X-Debian-Package: __SOURCE__
+Subject: Template Advisory __ADVISORY__
+
+------------------------------------------------------------------------
+Debian Security Advisory __ADVISORY__                  security@debian.org
+http://www.debian.org/security/                         __WHOAMI__
+__DATE__                   http://www.debian.org/security/faq
+------------------------------------------------------------------------
+
+Package        : __PACKAGE__
+Vulnerability  : XXX
+Problem type   : local/remote XXX
+Debian-specific: XXX
+CVE Id(s)      : XXX
+CERT advisory  : XXX
+BugTraq ID     : XXX
+Debian Bug     : XXX
+
+Several local/remote vulnerabilities have been discovered in...
+The Common
+Vulnerabilities and Exposures project identifies the following problems:
+
+[single issue]
+Foo discovered that
+
+
+[single issue]
+For the stable distribution (etch), this problem has been fixed in version XXX
+__PACKAGE__
+
+For the unstable distribution (sid), this problem has been fixed in
+version XXX
+
+[multiple issues]
+For the stable distribution (etch), these problems have been fixed in version
+__PACKAGE__
+
+For the unstable distribution (sid), these problems have been fixed in
+version XXX
+
+We recommend that you upgrade your __PACKAGE__ package.
+
+Upgrade instructions
+--------------------
+
+wget url
+        will fetch the file for you
+dpkg -i file.deb
+        will install the referenced file.
+
+If you are using the apt-get package manager, use the line for
+sources.list as given below:
+
+apt-get update
+        will update the internal database
+apt-get upgrade
+        will install corrected packages
+
+You may use an automated update by adding the resources from the
+footer to the proper configuration.
+
+
+Debian GNU/Linux 4.0 alias etch
+-------------------------------
+
+__ADVISORY_TEXT__
+
+
+  These files will probably be moved into the stable distribution on
+  its next update.
+
+---------------------------------------------------------------------------------
+For apt-get: deb http://security.debian.org/ stable/updates main
+For dpkg-ftp: ftp://security.debian.org/debian-security dists/stable/updates/main
+Mailing list: debian-security-announce@lists.debian.org
+Package info: `apt-cache show <pkg>' and http://packages.debian.org/<pkg>
diff --git a/tools/debianqueued-0.9/ChangeLog b/tools/debianqueued-0.9/ChangeLog
new file mode 100644 (file)
index 0000000..42e62ea
--- /dev/null
@@ -0,0 +1,343 @@
+2008-10-05  Thomas Viehmann  <tv@beamnet.de>
+
+       * show-deferred: make non-new uploads in deferred accessible
+
+2008-09-22  Thomas Viehmann  <tv@beamnet.de>
+
+       * show-deferred: minor fixes
+
+2008-09-21  Joerg Jaspert  <joerg@debian.org>
+
+       * debianqueued: Use perltidy
+       (copy_to_target): Only check md5sums if we want it, using a new
+       config value for it.
+
+       * Queue.README: Its ftp.upload.debian.org now, not
+       ftp-master.debian.org.
+
+       * Queue.README.ravel: New file for ravel
+
+       * config-upload: New file, used for ravel
+
+2008-09-20  Thomas Viehmann  <tv@beamnet.de>
+
+       * show-deferred: status page for deferred upload queue
+
+2008-09-20  Joerg Jaspert  <joerg@debian.org>
+
+       * Queue.README (Version): Update the text to match reality with
+       DEFERRED/DELAYED and the removed mv command
+
+2008-09-20  Thomas Viehmann  <tv@beamnet.de>
+
+       * debianqueued: Minor fixes on .commands processing.
+
+2008-09-15  Joerg Jaspert  <joerg@debian.org>
+
+       * config: Use 15 delayed dirs. Also change maintainer_mail to
+       ftpmaster. And remove lotsa ancient cvs history foo
+
+2008-09-11  Thomas Viehmann  <tv@beamnet.de>
+
+       * debianqueued: Add DELAYED-support.
+
+2008-06-15  Joerg Jaspert  <joerg@debian.org>
+
+       * debianqueued: Fix a brown-paper-bag bug (we just dont know who
+       to assign the bag too). strftime %b is better than %B for
+       the month name.
+
+2008-06-14  Joerg Jaspert  <joerg@debian.org>
+
+       * debianqueued (process_commands): Add a little note that one
+       should use dcut for .commands files
+
+2008-05-10  Stephen Gran   <sgran@debian.org>
+       * debianqueued: First pass at a send_mail implementation that 
+         sucks less.  This also gives us X-Debian-Package
+
+2008-05-08  Joerg Jaspert  <joerg@debian.org>
+
+       * debianqueued: added header X-Debian: DAK
+
+-- Version 0.9 released
+
+1999-07-07  Linux FTP-Administrator  <ftplinux@ftp.rrze.uni-erlangen.de>
+
+       * debianqueued: Implemented new upload methods "copy" and "ftp" as
+       alternatives to "ssh". "copy" simply copies files to another
+       directory on the queue host, "ftp" uses FTP to upload files. Both
+       of course need no ssh-agent.
+       New config vars:
+         $upload_method, $ftptimeout, $ftpdebug, $ls, $cp, $chmod,
+       Renamed config vars:
+         $master -> $target
+         $masterlogin -> $targetlogin
+         $masterdir -> $targetdir
+         $chmod_on_master -> $chmod_on_target
+
+       Note that the FTP method has some limitations: If no SITE MD5SUM
+       command is supported by the server, uploaded files can be verified
+       by their size only. And if removing of files in the target dir
+       isn't allowed, upload errors can't be handled gracefully.
+
+       * debianqueued: .changes files can now also be signed by GnuPG.
+
+       * dqueued-watcher: Also updates debian-keyring.gpg.
+       
+Tue Dec  8 14:09:44 1998  Linux FTP-Administrator  <ftplinux@ftp.rrze.uni-erlangen.de>
+
+       * debianqueued (process_changes): After an upload, do not remove
+       files with the same name stem if a .changes file is among them.
+       Then there is probably a second upload for a different
+       version/architecture.
+
+-- Version 0.8 released
+
+Thu May 14 16:17:48 1998  Linux FTP-Administrator  <ftplinux@ftp.rrze.uni-erlangen.de>
+
+       * debianqueued (process_changes): When --after a successfull
+       upload-- deleting files that seem to belong to the same job, check
+       for equal revision number on files that have one. It has happened
+       that the daemon deleted files that belonged to another job with
+       different revision, which shouldn't happen. The current algorithm
+       is more conservative, i.e. it tends not to delete such files. They
+       will be removed as stray files anyway after some time.
+
+Tue Apr 21 10:29:01 1998  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (check_incoming_writable): Also recognize
+       "read-only filesystem" as an error message that makes the daemon
+       think the incoming is unwritable.
+
+       * debianqueued (check_dir): Break from the .changes loop if
+       $incoming_writable has become cleared.
+
+       * debianqueued (process_changes): Don't increment failure count if
+       upload failed due to incoming dir being unwritable.
+
+       * debianqueued (check_dir): Don't use return value of
+       debian_file_stem as regexp, it's a shell pattern.
+       
+Tue Mar 31 11:06:11 1998  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (process_changes, process_commands): Check for
+       improper mail addresses from Maintainer: fields and try to handle
+       them by looking up the string in the Debian keyring. New funtion
+       try_to_get_mail_addr for the latter.
+
+       * debianqueued (fatal_signal): Kill status daemon only if it has
+       been started.
+
+       * debianqueued (copy_to_master): Change mode of files uploaded to
+       master explicitly to 644. scp uses the permission from the
+       original files, and those could be restricted due to local upload
+       policies.
+
+Mon Mar 30 13:24:51 1998  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * dqueued-watcher (main): If called with arguments, only make
+       summaries for the log files given. With this, you can view the
+       summaries also between normal watcher runs.
+       
+       * dqueued-watcher (make_summary): New arg $to_stdout, to print
+       report directly to stdout instead of sending via mail.
+
+Tue Mar 24 14:18:18 1998  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (check_incoming_writable): New function that checks
+       if the incoming dir on master is writable (it isn't during a
+       freeze is done). The check is triggered if an upload fails due to
+       "permission denied" errors. Until the incoming is writable again,
+       the queue is holded and no uploads are tried (so that the max.
+       number of tries isn't exceeded.)
+
+-- Version 0.7 released
+
+Mon Mar 23 13:23:20 1998  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (process_changes): In an upload failure message,
+       say explicitly that the job will be retried, to avoid confusion of
+       users.
+
+       * debianqueued (process_changes): $failure_file was put on
+       @keep_list only for first retry.
+
+       * debianqueued (process_changes): If the daemon removes a
+       .changes, set SGID bit on all files associated with it, so that
+       the test for Debian files without a .changes doesn't find them.
+       
+       * debianqueued (check_dir): Don't send reports for files without a
+       .changes if the files look like a recompilation for another
+       architecture. Then the maintainer extracted from the files isn't
+       the uploader. A job is treated like that if it doesn't include a
+       .dsc file and no *_{i386,all}.deb files.
+
+       * debianqueued (check_dir): Also don't send such a report if the
+       list of files with the same stem contains a .changes. This can be
+       the case if an upload failed and the .changes is still around, and
+       there's some file with the same name stem but which isn't in the
+       .changes (e.g. .orig.tar.gz).
+       
+       * debianqueued (process_changes): Set @keep_list earlier, before
+       PGP and non-US checks.
+
+       * debianqueued (main): Fix recognition of -k argument.
+       
+Tue Feb 17 11:54:33 1998  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (check_dir): Added test for binaries that could
+       reside on slow NFS filesystems. It is specially annoying if pgp
+       isn't found, because then the .changes is deleted. If one of the
+       files listed in @conf::test_binaries isn't present immediately
+       before a queue run, that one is delayed.
+
+-- Version 0.6 released
+
+Tue Dec  9 14:53:23 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (process_changes): Reject jobs whose package name
+       is in @nonus_packages (new config var). These must be uploaded to
+       nonus.debian.org instead of master itself.
+
+Tue Nov 25 11:02:38 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (main): Implemented -k and -r arguments (kill or
+       restart daemon, resp.)
+       
+       * debianqueued (is_debian_file): Exclude orig.tar.gz files from
+       that class, so that the maintainer address isn't searched in them
+       if they happen to come first in the dir.
+
+       * debianqueued (END): Fix kill call (pid and signo were swapped)
+
+       * debianqueued (process_changes): Moved check if job is already on
+       master to a later stage, to avoid connecting to master as long as
+       there are still errors with the job (missing files or the like).
+
+       * debianqueued (check_alive): Lookup master's IP address before
+       every ping, it could change while the daemon is running...
+
+-- Version 0.5 released
+
+Mon Nov 11 14:37:52 1997  Linux FTP-Administrator <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (process_commands): rm command now can process more
+       than one argument and knows about wildcards
+       
+Mon Nov  6 15:09:53 1997  Linux FTP-Administrator <ftplinux@arachnia.rrze.uni-erlangen.de>
+       
+       * debianqueued (process_commands): Recognize commands on the same
+       line as the Commands: keyword, not only on continuation lines.
+       
+Mon Nov  3 16:49:57 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (close_log): After reopening the log file, write
+       one message it. This avoids that dqueued-watcher's rotating
+       algorithm delays from several minutes to a few hours on every
+       rotate, since it looks at the time of the first entry.
+
+Thu Oct 30 13:56:35 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * dqueued-watcher (make_summary): Added some new summary counters
+       for command files.
+
+       * debianqueued (process_changes): Added check for files that seem
+       to belong to an upload (match debian_file_stem($changes)), but
+       aren't listed in the .changes. Most probably these are unneeded
+       .orig.tar.gz files. They are deleted.
+
+       * debianqueued (print_status): Print revision and version number
+       of debianqueued in status file.
+
+       * debianqueued (process_commands): New function, for processing
+       the new feature of .command files. These enable uploaders to
+       correct mistakes in the queue dir (corrupted/misnamed files)
+       
+Wed Oct 29 15:35:03 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       *debianqueued (check_dir): Extra check for files that look like an
+       upload, but miss a .changes file. A problem report is sent to the
+       probable uploader after $no_changes_timeout seconds (new config
+       var). The maintainer email can be extracted from .dsc, .deb,
+       .diff.gz and .tar.gz files (though the maintainer needs not
+       necessarily be the uploader...) New utility functions
+       is_debian_file, get_maintainer, debian_file_stem.
+       
+       * debianqueued (pgp_check, get_maintainer): Quote filenames used
+       on sh command lines, so metacharacters in the names can't do bad
+       things. (Though wu-ftpd generally shouldn't allow uploading files
+       with such names.)
+
+       * debianqueued (print_time): Print times always as
+       hour:minute:second, i.e. don't omit the hour if it's 0. This could
+       confuse users, because they don't know if the hour or the seconds
+       are missing.
+
+-- Version 0.4 released
+
+Thu Sep 25 13:18:57 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (process_changes): Forgot to remove a bad .changes
+       file in some cases (no mail address, not PGP signed at all, no
+       files mentioned). Also initialize some variables to avoid Perl
+       warnings.
+
+Wed Sep 17 14:15:21 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * dqueued-watcher (make_summary): Add feature of writing summaries
+       also to a file. Config var do_summary renamed to mail_summary,
+       additional var summary_file.
+
+Mon Sep 15 11:56:59 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * dqueued-watcher: Log several activities of the watcher to the log
+       file; new function logger() for this.
+
+       * debianqueued (process_changes, check_alive): Make some things more
+       verbose in non-debug mode.
+
+Mon Aug 18 13:25:04 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * dqueued-watcher (rotate_log): Using the log file's ctime for
+       calculating its age was a rather bad idea -- starting the daemon
+       updates that time stamp. Now the first date found in the log file
+       is used as basis for age calculation.
+
+       * dqeued-watcher (make_summary): New function to build a summary
+       of daemon actions when rotating logs. Controlled by config
+       variable $do_summary.
+
+Tue Aug 12 13:26:52 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * Makefile: new files with targets for automating various
+       administrative tasks
+
+-- Version 0.3 released
+
+Mon Aug 11 10:48:31 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (is_on_master, copy_to_master): Oops, forget
+       alarm(0)'s to turn off timeouts again.
+
+       * debianqueued: Revised the startup scheme so that it also works
+       with the socket-based ssh-agent. That agent periodically checks
+       whether the process it started is still alive and otherwise exits.
+       For that, the go-into-background fork must be done before
+       ssh-agent is started.
+
+       * debianqueued: Implemented close_log and SIGHUP handling for
+       logfile rotating.
+
+       * dqueued-watcher: Implemented log file rotating.
+
+Thu Aug 07 11:25:22 1997  Linux FTP-Administrator  <ftplinux@arachnia.rrze.uni-erlangen.de>
+
+       * debianqueued (is_on_master, copy_to_master): added timeouts to
+       all ssh/scp operations, because I've seen one once hanging...
+
+-- Started ChangeLog
+-- Version 0.2 released
+
+$Id: ChangeLog,v 1.36 1999/07/08 09:43:24 ftplinux Exp $
+       
diff --git a/tools/debianqueued-0.9/Makefile b/tools/debianqueued-0.9/Makefile
new file mode 100644 (file)
index 0000000..2e8727e
--- /dev/null
@@ -0,0 +1,117 @@
+#
+# Makefile for debianqueued -- only targets for package maintainance
+#
+# $Id: Makefile,v 1.10 1998/03/25 09:21:01 ftplinux Exp $
+#
+# $Log: Makefile,v $
+# Revision 1.10  1998/03/25 09:21:01  ftplinux
+# Implemented snapshot target
+#
+# Revision 1.9  1998/03/23 14:10:28  ftplinux
+# $$num in make upload needs braces because _ follows
+#
+# Revision 1.8  1997/12/16 13:20:57  ftplinux
+# add _all to changes name in upload target
+#
+# Revision 1.7  1997/11/20 15:34:11  ftplinux
+# upload target should copy only current release to queue dir
+#
+# Revision 1.6  1997/09/29 14:28:38  ftplinux
+# Also fill in Version: for .changes file
+#
+# Revision 1.5  1997/09/25 11:33:48  ftplinux
+# Added automatic adding of release number to ChangeLog
+#
+# Revision 1.4  1997/08/18 11:29:11  ftplinux
+# Include new release number in message of cvs commits
+#
+# Revision 1.3  1997/08/12 10:39:08  ftplinux
+# Added generation of .changes file in 'dist' target; added 'upload'
+# target (using the queue :-)
+#
+# Revision 1.2  1997/08/12 10:01:32  ftplinux
+# Fixed dist target to work (last checkin was needed to test it at all)
+#
+#
+
+CVS = cvs
+RELNUMFILE = release-num
+# files that contain the release number
+FILES_WITH_NUM = debianqueued dqueued-watcher
+# name of cvs module
+MODULE = debianqueued
+
+.PHONY: default release dist
+
+default:
+       @echo "Nothing to make -- the Makefile is only for maintainance purposes"
+       @exit 1
+
+# Usage:
+#   make release (use number from file release-num)
+#  or
+#   make release RELNUM=x.y (writes new number to release-num)
+
+release:
+       if cvs status $(RELNUMFILE) | grep -q Up-to-date; then true; else \
+               echo "$(RELNUMFILE) needs commit first"; exit 1; \
+       fi
+ifdef RELNUM
+       echo $(RELNUM) >$(RELNUMFILE)
+       cvs commit -m "Bumped release number to `cat $(RELNUMFILE)`" $(RELNUMFILE)
+endif
+       perl -pi -e "s/Release: \S+/Release: `cat $(RELNUMFILE)`/;" \
+               $(FILES_WITH_NUM)
+       cvs commit -m "Bumped release number to `cat $(RELNUMFILE)`" $(FILES_WITH_NUM)
+       if grep -q "Version `cat release-num` released" ChangeLog; then true; else \
+               mv ChangeLog ChangeLog.orig; \
+               echo "" >ChangeLog; \
+               echo "-- Version `cat $(RELNUMFILE)` released" >>ChangeLog; \
+               echo "" >>ChangeLog; \
+               cat ChangeLog.orig >>ChangeLog; \
+               rm ChangeLog.orig; \
+               cvs commit -m "Bumped release number to `cat $(RELNUMFILE)`" ChangeLog; \
+       fi
+       cvs tag release-`cat $(RELNUMFILE) | sed 's/\./-/'`
+
+dist:
+       set -e; \
+       num=`cat $(RELNUMFILE)`; name=debianqueued-$$num; \
+       mkdir tmp; \
+       (cd tmp; cvs export -r release-`echo $$num | sed 's/\./-/'` $(MODULE); \
+        mv $(MODULE) $$name; \
+        tar cvf ../../$$name.tar $$name); \
+       gzip -9f ../$$name.tar; \
+       rm -rf tmp; \
+       file=../$$name.tar.gz; \
+       md5=`md5sum $$file | awk -e '{print $$1}'`; \
+       size=`ls -l $$file | awk -e '{print $$4}'`; \
+       chfile=../debianqueued_`cat $(RELNUMFILE)`_all.changes; \
+       sed -e "s/^Date: .*/Date: `822-date`/" -e "s/Version: .*/Version: `cat $(RELNUMFILE)`/" <changes-template >$$chfile; \
+       echo " $$md5 $$size byhand - $$name.tar.gz" >>$$chfile; \
+       pgp -u 'Roman Hodek' +clearsig=on -fast <$$chfile >$$chfile.asc; \
+       mv $$chfile.asc $$chfile
+
+# can only be used on ftp.uni-erlangen.de :-)
+upload:
+       set -e; \
+       num=`cat $(RELNUMFILE)`; \
+       cp ../debianqueued-$$num.tar.gz ../debianqueued_$${num}_all.changes $$HOME/Linux/debian/UploadQueue
+
+# make snapshot from current sources
+snapshot:
+       set -e; \
+       modified=`cvs status 2>/dev/null | awk '/Status:/ { if ($$4 != "Up-to-date") print $$2 }'`; \
+       if [ "x$$modified" != "x" ]; then \
+               echo "There are modified files: $$modified"; \
+               echo "Commit first"; \
+               exit 1; \
+       fi; \
+       name=debianqueued-snapshot-`date +%y%m%d`; \
+       rm -rf tmp; \
+       mkdir tmp; \
+       (cd tmp; cvs export -D now $(MODULE); \
+        mv $(MODULE) $$name; \
+        tar cvf ../../$$name.tar $$name); \
+       gzip -9f ../$$name.tar; \
+       rm -rf tmp
diff --git a/tools/debianqueued-0.9/PROBLEMS b/tools/debianqueued-0.9/PROBLEMS
new file mode 100644 (file)
index 0000000..139fb59
--- /dev/null
@@ -0,0 +1,29 @@
+
+This is a list of problems that I have seen:
+
+ - One an upload failed with the following error:
+
+     Jul  8 12:13:53 Upload to master.debian.org failed, last exit status 1
+     Jul  8 12:13:53 Error messages from scp:
+     bind: Permission denied
+     lost connection
+
+   Never seen such an error from ssh/scp before... But since it didn't
+   happen again, I suspect something with master and/or the net.
+
+ - There are some protocol problems between certain ssh version (on
+   client/server side). The effect is that scp either hangs itself
+   (times out after $remote_timeout), or leaves ssh processes hanging
+   around. I've noticed that with ssh 1.2.19 on the server. I have a
+   prototype for a workaround, but haven't included it in
+   debianqueued, because master has been updated to 1.2.20 now and the
+   problem disappeared.
+
+ - The "ftp" method has some limitiations:
+       1) Files in the target dir can't be deleted.
+       2) Uploaded files can't be verified as good as with the other methods.
+       3) $chmod_on_target often doesn't work.
+       4) The check for a writable incoming directory leaves temporary files
+          behind.
+
+$Id: PROBLEMS,v 1.4 1999/07/08 09:34:52 ftplinux Exp $
diff --git a/tools/debianqueued-0.9/Queue.README b/tools/debianqueued-0.9/Queue.README
new file mode 100644 (file)
index 0000000..669fda3
--- /dev/null
@@ -0,0 +1,95 @@
+
+This directory is the Debian upload queue of ftp.upload.debian.org. All
+files uploaded here will be moved into the project incoming dir on
+this machine.
+
+Only known Debian developers can upload here. Uploads have to be signed
+by PGP keys in the Debian keyring. Files not meeting this criterion or
+files not mentioned in a .changes file will be removed after some time.
+
+The queue daemon will notify you by mail of success or any problems
+with your upload.
+
+
+*.commands Files
+----------------
+
+Besides *.changes files, you can also upload *.commands files for the
+daemon to process. With *.commands files, you can instruct the daemon
+to remove or rename files in the queue directory that, for example,
+resulted from failed or interrupted uploads. A *.commands file looks
+much like a *.changes, but contains only two fields: Uploader: and
+Commands:. It must be PGP-signed by a known Debian developer, to avoid
+that E.V.L. Hacker can remove/rename files in the queue. The basename
+(the part before the .commands extension) doesn't matter, but best
+make it somehow unique.
+
+The Uploader: field should contain the mail address to which the reply
+should go, just like Maintainer: in a *.changes. Commands: is a
+multi-line field like e.g. Description:, so each continuation line
+should start with a space. Each line in Commands: can contain a
+standard 'rm' command, but no options are allowed. Except for the
+DELAYED queue (see below) filenames may not contain slashes (so that
+they're restricted to the queue directory). 'rm' can process as much
+arguments as you give it (not only one), and also knows about the shell
+wildcards *, ?, and [].
+
+Example of a *.commands file:
+
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Uploader: Some One <some@example.com>
+Commands: 
+ rm hello_1.0-1_i386.deb
+
+-----BEGIN PGP SIGNATURE-----
+Version: 2.6.3ia
+
+[...]
+-----END PGP SIGNATURE-----
+
+
+DELAYED Queue
+-------------
+There is a DELAYED queue available. Packages uploaded into the
+X-day (X between 0 and 15) subdirectories of DELAYED/ will be moved into
+the DEFERRED queue and won't be placed into the archive before the
+waiting time is over.
+
+To avoid confusion, the terms used are:
+DELAYED  - the public ftp upload directories, reachable via DELAYED/X-day
+
+DEFERRED - this is the place where the uploads are placed by the queue
+           daemon after processing and where packages wait before they
+           are moved into the incoming queue.
+
+
+You can modify the queues (besides uploading packages) with *.commands
+files as described above, using the following syntax.
+
+Note that any processing in the DEFERRED queue works on whole uploads
+(i.e. a .changes file and all the files that came with it), whereas
+operations in the DELAYED queue (and the normal ftp root directory)
+work on individual files.
+
+ - To move a package from one DEFERRED directory into another, say
+   from 8-days to 2-days delay:
+   reschedule foo_1.2-1.1_all.changes 2-day
+
+   The move-target has to be without the trailing /.
+
+ - To delete an upload (and all associated files) in the DEFERRED queue:
+   cancel foo_1.2-1.1_all.changes
+
+ - To delete a broken upload in the DELAYED queue:
+   rm DELAYED/X-day/foobar.deb
+
+   or
+
+   rm --searchdirs foobar.deb
+
+ - The old mv command is no longer supported.
+
+Wildcards in .commands files are only valid for the DELAYED queue and
+its rm command, the DEFERRED queue commands cancel and reschedule do
+not allow them.
diff --git a/tools/debianqueued-0.9/Queue.README.ravel b/tools/debianqueued-0.9/Queue.README.ravel
new file mode 100644 (file)
index 0000000..525a3e0
--- /dev/null
@@ -0,0 +1,47 @@
+
+This directory is the Debian upload queue of ssh.upload.debian.org. All
+valid files uploaded here will be transferred to ftp.upload.debian.org.
+
+Only known Debian developers can upload here. Uploads have to be signed
+by PGP keys in the Debian keyring. Files not meeting this criterion or
+files not mentioned in a .changes file will be removed after some time.
+
+The queue daemon will notify you by mail of success or any problems
+with your upload.
+
+
+*.commands Files
+----------------
+
+Besides *.changes files, you can also upload *.commands files for the
+daemon to process. With *.commands files, you can instruct the daemon
+to remove or rename files in the queue directory that, for example,
+resulted from failed or interrupted uploads. A *.commands file looks
+much like a *.changes, but contains only two fields: Uploader: and
+Commands:. It must be PGP-signed by a known Debian developer, to avoid
+that E.V.L. Hacker can remove/rename files in the queue. The basename
+(the part before the .commands extension) doesn't matter, but best
+make it somehow unique.
+
+The Uploader: field should contain the mail address to which the reply
+should go, just like Maintainer: in a *.changes. Commands: is a
+multi-line field like e.g. Description:, so each continuation line
+should start with a space. Each line in Commands: can contain a
+standard 'rm' command, but no options are allowed. Filenames may not
+contain slashes (so that they're restricted to the queue
+directory). 'rm' can process as much arguments as you give it (not only
+one), and also knows about the shell wildcards *, ?, and [].
+
+Example of a *.commands file:
+
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Uploader: Some One <some@example.com>
+Commands: 
+ rm hello_1.0-1_i386.deb
+
+-----BEGIN PGP SIGNATURE-----
+Version: 2.6.3ia
+
+[...]
+-----END PGP SIGNATURE-----
diff --git a/tools/debianqueued-0.9/Queue.message b/tools/debianqueued-0.9/Queue.message
new file mode 100644 (file)
index 0000000..1653f57
--- /dev/null
@@ -0,0 +1,3 @@
+
+This directory is the Debian upload queue of ftp.uni-erlangen.de. Only
+known Debian developers can upload here.
diff --git a/tools/debianqueued-0.9/README b/tools/debianqueued-0.9/README
new file mode 100644 (file)
index 0000000..3f5435a
--- /dev/null
@@ -0,0 +1,724 @@
+          debianqueued -- daemon for managing Debian upload queues
+          ========================================================
+
+Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+$Id: README,v 1.20 1999/07/08 09:35:37 ftplinux Exp $
+
+
+Copyright and Disclaimer
+------------------------
+
+This program is free software.  You can redistribute it and/or
+modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation: either version 2 or
+(at your option) any later version.
+
+This program comes with ABSOLUTELY NO WARRANTY!
+
+You're free to modify this program at your will, according to the GPL,
+and I don't object if you modify the program. But it would be nice if
+you could send me back such changes if they could be of public
+interest. I will try to integrate them into the mainstream version
+then.
+
+
+Installation
+------------
+
+debianqueued has been written for running a new Debian upload queue at
+ftp.uni-erlangen.de, but I tried to keep it as general as possible and
+it should be useable for other sites, too. If there should be
+non-portabilities, tell me about them and we'll try to get them fixed!
+
+Before installing debianqueued, you should have the following
+utilities installed:
+
+ - pgp (needed for checking signatures)
+
+ - ssh & Co. (but not necessarily sshd, only client programs used)
+
+ - md5sum (for checking file integrity)
+
+ - mkfifo (for creating the status FIFO)
+
+ - GNU tar
+
+ - gzip
+
+ - ar (for analyzing .deb files)
+
+The daemon needs a directory of its own where the scripts reside and
+where it can put certain files. This directory is called $queued_dir
+in the Perl scripts and below. There are no special requirements where
+in the filesystem hierarchy this directory should be.
+
+All configurations are done in file 'config' in $queued_dir. For
+security reasons, the $queued_dir should not be in a public FTP area,
+and should be writeable (as the files in it) only for the user
+maintaining the local debianqueued.
+
+The file Queue.README and Queue.message in the distribution archive
+are examples for README and .message files to put into the queue
+directory. Modify them as you like, or don't install them if you
+don't like them...
+
+
+Running debianqueued
+--------------------
+
+debianqueued is intended to run all time, not as a cron job.
+Unfortunately, you can't start it at system boot time automatically,
+because a human has to type in the pass phrase for the ssh key. So you
+have to start the daemon manually.
+
+The daemon can be stopped by simply killing it (with SIGTERM
+preferrably). SIGTERM and SIGINT are blocked during some operations,
+where it could leave files in a inconsistent state. So it make take
+some time until the daemon really dies. If you have the urgent need
+that it goes away immediately, use SIGQUIT. Please don't use SIGKILL
+except unavoidable, because the daemon can't clean up after this
+signal.
+
+For your convenience, the daemon can kill and restart itself. If you
+start debianqueued with a "-k" argument, it tries to kill a running
+daemon (and it complains if none is running.) If "-r" is on the
+command line, it tries to kill a running daemon first if there is one.
+(If not, it starts anyway, but prints a little warning.) If a daemon
+is running and a new one is started without "-r", you get an error
+message about this. This is to protect you from restarting the daemon
+without intention.
+
+The other script, dqueued-watcher, is intended as cron job, and it
+watches that the daemon is running, in case that it should crash
+sometimes. It also takes care of updating the Debian keyring files if
+necessary. You should enter it e.g. like
+
+  0,30 *   *   *   *    .../dqueued-watcher
+
+into your crontab. (Assuming you want to run it every 30 minutes,
+which seems a good compromise.)
+
+Both scripts (debianqueued and dqueued-watcher) need no special
+priviledges and thus can be run as an ordinary user (not root). You
+can create an own user for debianqueued (e.g. "dqueue"), but you need
+not. The only difference could be which ssh key is used for connects
+to the target host. But you can configure the file to take the ssh key
+from in the config file.
+
+
+The Config File
+---------------
+
+The config file, $queued_dir/config, is plain Perl code and is
+included by debianqueued and dqueued-watcher. You can set the
+following variables there:
+
+ - $debug:
+   Non-zero values enable debugging output (to log file).
+
+The following are all programs that debianqueued calls. You should
+always use absolute pathnames!
+
+ - $pgp, $ssh, $scp, $ssh_agent, $ssh_add, $md5sum, $mail, $mkfifo,
+   $tar, $gzip, $ar
+
+   Notes:
+
+    o $mail should support the -s option for supplying a subject.
+      Therefore choose mailx if your mail doesn't know -s.
+
+    o $tar should be GNU tar, several GNU features are used (e.g.
+      --use-compress-program).
+
+    o $ar must be able to unpack *.deb files and must understand the
+      'p' command. Better check this first... If you don't define $ar
+      (or define it to be empty), debianqueued won't be able to
+      extract a maintainer address from .deb files. (Which isn't that
+      disturbing...)
+
+ - @test_binaries:
+
+   All binaries listed in this variable are tested to be present
+   before each queue run. If any is not available, the queue run is
+   delayed. This test can be useful if those binaries reside on NFS
+   filesystems which may be (auto-)mounted only slowly. It is
+   specially annoying for users if pgp can't be found and a .changes
+   is deleted.
+
+ - $ssh_options:
+   Options passed to ssh and scp on every call. General ssh
+   configuration should be done here and not in ~/.ssh/config, to
+   avoid dependency on the user's settings. A good idea for
+   $ssh_options seems to be
+
+     -o'BatchMode yes' -o'FallBackToRsh no' -o'ForwardAgent no'
+     -o'ForwardX11 no' -o'PasswordAuthentication no'
+     -o'StrictHostKeyChecking yes'
+
+ - $ssh_key_file:
+   The file containing the ssh key you want the daemon to use for
+   connects to the target host. If you leave this empty, the default
+   ~/.ssh/identity is used, which may or may not be what you want.
+
+ - $incoming:
+   This names the queue directory itself. Probably it will be inside
+   the public FTP area. Don't forget to allow uploads to it in
+   ftpaccess if you're using wu-ftpd.
+
+   Maybe you should also allow anonymous users to rename files in that
+   directory, to fix upload problems (they can't delete files, so they
+   have to move the errorneous file out of the way). But this
+   introduces a denial-of-service security hole, that an attacker
+   renames files of other people and then a job won't be done. But at
+   least the data aren't lost, and the rename command probably was
+   logged by ftpd. Nevertheless, there's no urgent need to allow
+   renamings, because the queue daemon deletes all bad files
+   automatically, so they can be reuploaded under the same name.
+   Decide on your own...
+
+ - $keep_files:
+   This is a regular expression for files that never should be deleted
+   in the queue directory. The status file must be included here,
+   other probable candicates are .message and/or README files.
+
+ - $chmod_on_target:
+   If this variable is true (i.e., not 0 or ""), all files belonging
+   to a job are changed to mode 644 only on the target host. The
+   alternative (if the variable is false, i.e. 0) is to change the
+   mode already locally, after the sizes and md5 sums have been
+   verified. The latter is the default.
+
+   The background for this is the following: The files must be
+   word-readable on master for dinstall to work, so they must be at
+   least mode 444, but 644 seems more useful. If the upload policy of
+   your site says that uploaded files shouldn't be readable for world,
+   the queue daemon has to change the permission at some point of
+   time. (scp copies a file's permissions just as the contents, so
+   after scp, the files on the target have the same mode as in the
+   queue directory.) If the files in the queue are mode 644 anyway,
+   you don't need to care about this option. The default --to give
+   word read permission in the queue already after some checks-- is
+   obviously less restrictive, but might be against the policy of your
+   site. The alternative keeps the files unreadable in the queue in
+   any case, and they'll be readable only on the target host.
+
+ - $statusfile:
+   This is the name of the status file or FIFO, through which users
+   can ask the daemon what it's currently doing. It should normally be
+   in the queue directory. If you change the name, please don't forget
+   to check $keep_files. See also the own section on the status file.
+
+   If you leave $statusfile empty, the daemon doesn't create and
+   manage a status file at all, if you don't want it. Unfortunately,
+   dqueued-watcher's algorithm to determine whether it already has
+   reported a missing daemon depends on the status file, so this
+   doesn't work anymore in this case. You'll get dead daemon mails on
+   every run of dqueued-watcher.
+
+ - $statusdelay:
+   If this number is greater than 0, the status file is implemented as
+   a regular file, and updated at least every $statusdelay seconds. If
+   $statusdelay is 0, the FIFO implementation is used (see status file
+   section).
+
+ - $keyring:
+   The name of the PGP keyring the daemon uses to check PGP signatures
+   of .changes files. This is usually $queued_dir/debian-keyring.pgp.
+   It should contain exactly the keys of all Debian developers (i.e.
+   those and no other keys).
+
+ - $gpg_keyring:
+   The name of the GnuPG keyring. The daemon now alternatively accepts
+   GnuPG signatures on .changes and .commands files. The value here is
+   usually $queued_dir/debian-keyring.gpg. It should contain only keys
+   of Debian developers (but not all developers have a GPG key
+   yet...).
+
+ - $keyring_archive:
+   Path of the debian-keyring.tar.gz file inside a Debian mirror. The
+   file is "/debian/doc/debian-keyring.tar.gz" on ftp.debian.org,
+   don't know where you mirror it to... Leave it empty if you don't
+   have that file on your local machine. But then you'll have to
+   update the keyring manually from time to time.
+
+ - $keyring_archive_name:
+   Name of the PGP keyring file in the archive $keyring_archive. Currently
+   "debian-keyring*/debian-keyring.pgp".
+
+ - $gpg_keyring_archive_name:
+   Name of the GnuPG keyring file in the archive $keyring_archive. Currently
+   "debian-keyring*/debian-keyring.gpg".
+
+ - $logfile:
+   The file debianqueued writes its logging data to. Usually "log" in
+   $queued_dir.
+
+ - $pidfile:
+   The file debianqueued writes its pid to. Usually "pid" in
+   $queued_dir.
+
+ - $target:
+   Name of the target host, i.e. the host where the queue uploads to.
+   Usually "master.debian.org". (Ignored with "copy" upload method.)
+
+ - $targetlogin:
+   The login on the target to use for uploads. (Ignored with "copy"
+   and "ftp" upload methods; "ftp" always does anonymous logins.)
+
+ - $targetdir:
+   The directory on the target to where files should be uploaded. On
+   master.debian.org this currently is
+   "/home/Debian/ftp/private/project/Incoming".
+
+ - $max_upload_retries:
+   This is the number how often the daemon tries to upload a job (a
+   .changes + the files belonging to it). After that number is
+   exhausted, all these files are deleted.
+
+ - $log_age:
+   This is how many days are waited before logfiles are rotated. (The
+   age of the current log files is derived from the first date found
+   in it.)
+
+ - $log_keep:
+   How many old log files to keep. The current logfile is what you
+   configured as $logfile above, older versions have ".0", ".1.gz",
+   ".2.gz", ... appended. I.e., all old versions except the first are
+   additionally gzipped. $log_keep is one higher than the max.
+   appended number that should exist.
+
+ - $mail_summary:
+   If this is set to a true value (not 0 and not ""), dqueued-watcher
+   will send a mail with a summary of the daemon's acivities whenever
+   logfiles are rotated.
+
+ - $summary_file:
+   If that value is a file name (and not an empty string),
+   dqueued-watcher will write the same summary of daemon activities as
+   above to the named file. This can be in addition to sending a mail.
+
+ - @nonus_packages:
+   This is a (Perl) list of names of packages that must be uploaded to
+   nonus.debian.org and not to master. Since the queue daemon only can
+   deal with one target, it can't do that upload and thus must reject
+   the job. Generally you can treat this variable as a list of any
+   packages that should be rejected.
+
+All the following timing variables are in seconds:
+
+ - $upload_delay_1:
+   The time between the first (failed) upload try and the next one.
+   Usually shorter then $upload_delay_2 for quick retry after
+   transient errors.
+
+ - $upload_delay_2:
+   The time between the following (except the first) upload retries.
+
+ - $queue_delay:
+   The time between two queue runs. (May not be obeyed too exactly...
+   a few seconds deviation are normal).
+
+ - $stray_remove_timeout:
+   If a file not associated with any .changes file is found in the
+   queue directory, it is removed after this many seconds.
+
+ - $problem_report_timeout:
+   If there are problems with a job that could also be result of a
+   not-yet-complete upload (missing or too small files), the daemon
+   waits this long before reporting the problem to the uploader. This
+   avoids warning mails for slow but ongoing uploads.
+
+ - $no_changes_timeout:
+
+   If files are found in the queue directory that look like a Debian
+   upload (*.tar.gz, *.diff.gz, *.deb, or *.dsc files), but aren't
+   accompanied by a .changes file, then debianqueued tries to notify
+   the uploader after $no_changes_timeout seconds about this. This
+   value is somewhat similar to $problem_report_timeout, and the
+   values can be equal.
+
+   Since there's no .changes, the daemon can't never be sure who
+   really uploaded the files, but it tries to extract the maintainer
+   address from all of the files mentioned above. If they're real
+   Debian files (except a .orig.tar.gz), this works in most cases.
+
+ - $bad_changes_timeout:
+   After this time, a job with persisting problems (missing files,
+   wrong size or md5 checksum) is removed.
+
+ - $remote_timeout:
+   This is the maximum time a remote command (ssh/scp) may take. It's
+   to protect against network unreliabilities and the like. Choose the
+   number sufficiently high, so that the timeout doesn't inadventedly
+   kill a longish upload. A few hours seems ok.
+
+Contents of $queued_dir
+-----------------------
+
+$queued_dir contains usually the following files:
+
+ - config:
+   The configuration file, described above.
+
+ - log:
+   Log file of debianqueued. All interesting actions and errors are
+   logged there, in a format similar to syslog.
+
+ - pid:
+   This file contains the pid of debianqueued, to detect double
+   daemons and for killing a running daemon.
+
+ - debian-keyring.pgp, debian-keyring.gpg:
+   These are the PGP and GnuPG key rings used by debianqueued to
+   verify the signatures of .changes files. It should contain the keys
+   of all Debian developers and no other keys. The current Debian key
+   ring can be obtained from
+   ftp.debian.org:/debian/doc/debian-keyring.tar.gz. dqueued-watcher
+   supports the facility to update this file automatically if you also
+   run a Debian mirror.
+
+ - debianqueued, dqueued-watcher:
+   The Perl scripts.
+
+All filenames except "config" can be changed in the config file. The
+files are not really required to reside in $queued_dir, but it seems
+practical to have them all together...
+
+
+Details of Queue Processing
+---------------------------
+
+The details of how the files in the queue are processed may be a bit
+complicated. You can skip this section if you're not interested in
+those details and everything is running fine... :-)
+
+The first thing the daemon does on every queue run is determining all
+the *.changes files present. All of them are subsequently read and
+analyzed. The .changes MUST contain a Maintainer: field, and the
+contents of that field should be the mail address of the uploader. The
+address is used for sending back acknowledges and error messages.
+(dinstall on master uses the same convention.)
+
+Next, the PGP or GnuPG signature of the .changes is checked. The
+signature must be valid and must belong to one of the keys in the
+Debian keyring (see config variables $keyring and $gpg_keyring). This
+ensures that only registered Debian developers can use the upload
+queue to transfer files to master.
+
+Then all files mentioned in the Files: field of the .changes are
+checked. All of them must be present, and must have correct size and
+md5 checksum. If any of this conditions is violated, the upload
+doesn't happen and an error message is sent to the uploader. If the
+error is a incorrect size/md5sum, the file is also deleted, because it
+has to be reuploaded anyway, and it could be the case that the
+uploader cannot easily overwrite a file in the queue dir (due to
+upload permission restrictions). If the error is a missing file or a
+too small file, the error message is hold back for some time
+($problems_report_timeout), because they can also be result of an
+not-yet-complete upload.
+
+The time baseline for when to send such a problem report is the
+maximum modification time of the .changes itself and all files
+mentioned in it. When such a report is sent, the setgid bit (show as
+'S' in ls -l listing, in group x position) on the .changes is set to
+note that fact, and to avoid the report being sent on every following
+queue run. If any modification time becomes greater than the time the
+setgid bit was set, a new problem report is sent, because obviously
+something has changed to the files.
+
+If a job is hanging around for too long with errors
+($bad_changes_timeout), the .changes and all its files are deleted.
+The base for that timeout is again the maximum modification time as
+explained above.
+
+If now the .changes itself and all its files are ok, an upload is
+tried. The upload itself is done with scp. In that stage, various
+errors from the net and/or ssh can occur. All these simply count as
+upload failures, since it's not easy to distinguish transient and
+permanent failures :-( If the scp goes ok, the md5sums of the files on
+the target are compared with the local ones. This is to ensure that
+the transfer didn't corrupt anything. On any error in the upload or in
+the md5 check, the files written to the target host are deleted again
+(they may be broken), and an error message is sent to the uploader.
+
+The upload is retied $upload_delay_1 seconds later. If it fails again,
+the next retries have a (longer) delay $upload_delay_2 between them.
+At most $max_upload_retries retries are done. After all these failed,
+all the files are deleted, since it seems we can't move them... For
+remembering how many tries were alredy done (and when), debianqueued
+uses a separate file. Its name is the .changes' filename with
+".failures" appended. It contains simply two integers, the retry count
+and the last upload time (in Unix time format).
+
+After a successfull upload, the daemon also checks for files that look
+like they belonged to the same job, but weren't listed in the
+.changes. Due to experience, this happens rather often with
+.orig.tar.gz files, which people upload though they're aren't needed
+nor mentioned in the .changes. The daemon uses the filename pattern
+<pkg-name>_<version>* to find such unneeded files, where the Debian
+revision is stripped from <version>. The latter is needed to include
+.orig.tar.gz files, which don't have the Debian revision part. But
+this also introduces the possibility that files of another upload for
+the same package but with another revision are deleted though they
+shouldn't. However, this case seems rather unlikely, so I didn't care
+about it. If such files are deleted, that fact is mentioned in the
+reply mail to the uploader.
+
+If any files are found in the queue dir that don't belong to any
+.changes, they are considered "stray". Such files are remove after 
+$stray_remove_timeout. This should be around 1 day or so, to avoid
+files being removed that belong to a job, but whose .changes is still
+to come. The daemon also tries to find out whether such stray files
+could be part of an incomplete upload, where the .changes file is
+still missing or has been forgotten. Files that match the patterns
+*.deb, *.dsc, *.diff.gz, or *.tar.gz are analyzed whether a maintainer
+address can be extracted from them. If yes, the maintainer is notified
+about the incomplete upload after $no_changes_timeout seconds.
+However, the maintainer needs not really be the uploader... It could
+be a binary-only upload for another architecture, or a non-maintainer
+upload. In these cases, the mail goes to the wrong wrong person :-(
+But better than not writing at all, IMHO...
+
+
+The status file
+---------------
+
+debianqueued provides a status file for the user in the queue
+directory. By reading this file, the user can get an idea what the
+daemon is currently doing.
+
+There are two possible implementations of the status file: as a plain
+file, or as a named pipe (FIFO). Both have their advantages and
+disadvantages.
+
+If using the FIFO, the data printed (last ping time, next queue run)
+are always up to date, because they're interrogated (by a signal) just
+at the time the FIFO is opened for reading. Also, the daemon hasn't to
+care about the status file if nobody accesses it. The bad things about
+the FIFO: It is a potential portability problem, because not all
+systems have FIFOs, or they behave different than I expect... But the
+more severe problem: wu-ftpd refuses to send the contents of a FIFO on
+a FTP GET request :-(( It does an explicit check whether a file to be
+retrieved is a regular file. This can be easily patched [1], but not
+everybody wants to do that or can do that (but I did it for
+ftp.uni-erlangen.de). (BTW, there could still be problems (races) if
+more than one process try to read the status file at the same time...)
+
+The alternative is using a plain file, which is updated regularily by
+the daemon. This works on every system, but causes more overhead (the
+daemon has to wake up each $statusdelay seconds and write a file), and
+the time figures in the file can't be exact. $statusdelay should be a
+compromise between CPU wastage and desired accuracy of the times found
+in the status file. I think 15 or 30 seconds should be ok, but your
+milage may vary.
+
+If the status file is a FIFO, the queue daemon forks a second process
+for watching the FIFO (so don't wonder if debianqueued shows up twice
+in ps output :-), to avoid blocking a reading process too long until
+the main daemon has time to watch the pipe. The status daemon requests
+data from the main daemon by sending a signal (SIGUSR1). Nevertheless
+it can happen that a process that opens the status file (for reading)
+is blocked, because the daemon has crashed (or never has been started,
+after reboot). To minimize chances for that situation, dqueued-watcher
+replaces the FIFO by a plain file (telling that the daemon is down) if
+it sees that no queue daemon is running.
+
+
+  [1]: This is such a patch, for wu-ftpd-2.4.2-BETA-13:
+
+--- wu-ftpd/src/ftpd.c~        Wed Jul  9 13:18:44 1997
++++ wu-ftpd/src/ftpd.c Wed Jul  9 13:19:15 1997
+@@ -1857,7 +1857,9 @@
+         return;
+     }
+     if (cmd == NULL &&
+-        (fstat(fileno(fin), &st) < 0 || (st.st_mode & S_IFMT) != S_IFREG)) {
++        (fstat(fileno(fin), &st) < 0 ||
++       ((st.st_mode & S_IFMT) != S_IFREG &&
++        (st.st_mode & S_IFMT) != S_IFIFO))) {
+         reply(550, "%s: not a plain file.", name);
+         goto done;
+     }
+
+
+Command Files
+-------------
+
+The practical experiences with debianqueued showed that users
+sometimes make errors with their uploads, resulting in misnamed or
+corrupted files... Formerly they didn't have any chance to fix such
+errors, because the ftpd usually doesn't allow deleting or renaming
+files in the queue directory. (If you would allow this, *anybody* can
+remove/rename files, which isn't desirable.) So users had to wait
+until the daemon deleted the bad files (usually ~ 24 hours), before
+they could start the next try.
+
+To overcome this, I invented the *.command files. The daemon looks for
+such files just as it tests for *.changes files on every queue run,
+and processes them before the usual jobs. *.commands files must be PGP
+or GnuPG signed by a known Debian developer (same test as for
+*.changes), so only these people can give the daemon commands. Since
+Debian developers can also delete files in master's incoming, the
+*.commands feature doesn't give away any security.
+
+The syntax of a *.commands file is much like a *.changes, but it
+contains only two (mandatory) fields: Uploader: and Commands.
+Uploader: contains the e-mail address of the uploader for reply mails,
+and should have same contents as Maintainer: in a .changes. Commands:
+is a multi-line field like e.g. Description: or Changes:. Every
+continuation line must start with a space. Each line in Commands:
+contains a command for the daemon that looks like a shell command (but
+it isn't one, the daemon parses and executes it itself and doesn't use
+sh or the respective binaries).
+
+Example:
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Uploader: Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+Commands: 
+ rm hello_1.0-1_i386.deb
+ mv hello_1.0-1.dsx hello_1.0-1.dsc
+
+-----BEGIN PGP SIGNATURE-----
+Version: 2.6.3ia
+
+iQCVAwUBNFiQSXVhJ0HiWnvJAQG58AP+IDJVeSWmDvzMUphScg1EK0mvChgnuD7h
+BRiVQubXkB2DphLJW5UUSRnjw1iuFcYwH/lFpNpl7XP95LkLX3iFza9qItw4k2/q
+tvylZkmIA9jxCyv/YB6zZCbHmbvUnL473eLRoxlnYZd3JFaCZMJ86B0Ph4GFNPAf
+Z4jxNrgh7Bc=
+=pH94
+-----END PGP SIGNATURE-----
+
+The only commands implemented at this time are 'rm' and 'mv'. No
+options are implemented, and filenames may not contain slashes and are
+interpreted relative to the queue directory. This ensures that only
+files there can be modified. 'mv' always takes two arguments. 'rm' can
+take any number of args. It also knows about the following shell
+wildcard chars: *, ?, and [...]. {..,..} constructs are *not*
+supported. The daemon expands these patterns itself and doesn't use sh
+for that (for security reasons).
+
+*.commands files are processed before the usual *.changes jobs, so if
+a commands file fixes a job so that it can be processed, that
+processing happens in the same queue run and no unnecessary delay is
+introduced.
+
+The uploader of a *.commands will receive a reply mail with a comment
+(OK or error message) to each of the commands given. The daemon not
+only logs the contents of the Uploader: field, but also the owner of
+the PGP/GnuPG key that was used to sign the file. In case you want to
+find out who issued some commands, the Uploader: field is insecure,
+since its contents can't be checked.
+
+
+Security Considerations
+-----------------------
+
+You already know that debianqueued uses ssh & Co. to get access to
+master, or in general any target host. You also probably know that you
+need to unlock your ssh secret key with a passphrase before it can be
+used. For the daemon this creates a problem: It needs the passphrase
+to be able to use ssh/scp, but obviously you can't type in the phrase
+every time the daemon needs it... It would also be very ugly and
+insecure to write the passphase into some config file of the daemon!
+
+The solution is using ssh-agent, which comes with the ssh package.
+This agent's purpose is to store passphrases and give it to
+ssh/scp/... if they need it. ssh-agent has to ways how it can be
+accessed: through a Unix domain socket, or with an inherited file
+descriptor (ssh-agent is the father of your login shell then). The
+second method is much more secure than the first, because the socket
+can be easily exploited by root. On the other hand, an inherited file
+descriptor can be access *only* from a child process, so even root has
+bad chances to get its hands on it. Unfortunately, the fd method has
+been removed in ssh-1.2.17, so I STRONGLY recommend to use ssh-1.2.16.
+(You can still have a newer version for normal use, but separate
+binaries for debianqueued.) Also, using debianqueued with Unix domain
+sockets is basically untested, though I've heard that it doesn't
+work...
+
+debianqueued starts the ssh-agent automatically and runs ssh-add. This
+will ask you for your passphrase. The phrase is stored in the agent
+and available only to child processes of the agent. The agent will
+also start up a second instance of the queue daemon that notices that
+the agent is already running.
+
+Currently, there's no method to store the passphrase in a file, due to
+all the security disadvantages of this. If you don't mind this and
+would like to have some opportunity to do it nevertheless, please ask
+me. If there's enough demand, I'll do it.
+
+
+New Upload Methods
+------------------
+
+Since release 0.9, debianqueued has two new upload methods as
+alternatives to ssh: copy and ftp.
+
+The copy method simply moves the files to another directory on the
+same host. This seems a bit silly, but is for a special purpose: The
+admins of master intend to run an upload queue there, too, in the
+future to avoid non-anonymous FTP connections, which transmit the
+password in cleartext. And, additionally to simply moving the files,
+the queue daemon also checks the signature and integrity of uploads
+and can reject non-US packages.
+
+The ftp method uploads to a standard anon-FTP incoming directory. The
+intention here is that you could create second-level queue daemons.
+I.e., those daemons would upload into the queue of another daemon
+(and, for example, this could be the queue of the daemon on master).
+
+However, the ftp method still has some limitations:
+
+ 1) Files in the target dir can't be deleted.
+ 2) Uploaded files can't be verified as good as with the other methods.
+ 3) $chmod_on_target often doesn't work.
+ 4) The check for a writable incoming directory leaves temporary files
+    behind.
+
+Ad 1): In anon-FTP incoming directories removing of files usually
+isn't allowed (this would widely open doors to denial-of-service
+attacks). But debianqueued has to remove files on the target as part
+of handling upload errors. So if an transmission error happens during
+a job, the bad file can't be deleted. On the next try, the file is
+already present on the target and can't be overwritten, so all the
+following tries will fail, too, except the upstream queue daemon has
+deleted them already. And if the .changes was among the files already
+(at least partially) uploaded, the daemon even will think that the
+whole job is already present on the target and will delete the job in
+its queue.
+
+Ad 2): Uploaded files are usually verified with md5sum if they're
+really the same as the originals. But getting the md5sum for a file on
+a FTP server usually isn't possible. It's currently handled as
+follows: If the server supports a SITE MD5SUM command (non-standard!),
+then this is used and you have the same checking quality. Otherwise,
+debianqueued falls back to only comparing the file sizes. This is
+better than nothing, but doesn't detected changed contents that don't
+result in size changes.
+
+Ad 3): Often SITE CHMOD (standard) isn't allowed in incoming
+directories. If this is the case, $chmod_on_target must be off,
+otherwise all uploads will fail. The mode of uploaded files if forced
+anyway by the FTP server in most cases.
+
+Ad 4): As you know, the queue daemon has a special check if the target
+directory is writable at all (it isn't during a freeze) to protect
+against repeated upload errors. (Jobs would be even deleted otherwise
+if the target dir is unaccessible for too long.) This check is
+performed by creating a test file and deleting it immediately again.
+But since in FTP incoming dirs deletion isn't permitted, the temporary
+file ("junk-for-writable-test-DATE") will remain there. As a partial
+fix, the daemon deletes such files immediately, it doesn't even wait
+for $stray_remove_timeout. So if the upload goes to the queue dir of
+an upstream debianqueued, those temporary files won't be there for
+long.
+
+These problems of the FTP method might be remove in future, if I have
+better ideas how to bypass the limitations of anon-FTP incoming
+directories. Hints welcome :-)
+
+
+# Local Variables:
+# mode: indented-text
+# End:
diff --git a/tools/debianqueued-0.9/TODO b/tools/debianqueued-0.9/TODO
new file mode 100644 (file)
index 0000000..4a98842
--- /dev/null
@@ -0,0 +1,13 @@
+$Header: /allftp/CVS/debianqueued/TODO,v 1.8 1998/04/01 15:27:39 ftplinux Exp $
+
+ - There are numerous potential portability problems... They'll show
+   up as this script is used on more and different machines.
+
+ - There was a suggestion how bad files on uploads could be handled
+   easier than with command files: Give them some known extension
+   (e.g. .<digits>), and the daemon could look for those files if the
+   main file has bad size or md5.
+
+ - Make provisions for the (rare) case that the daemon looks at a
+   yet-incomplete .changes file.
+
diff --git a/tools/debianqueued-0.9/changes-template b/tools/debianqueued-0.9/changes-template
new file mode 100644 (file)
index 0000000..ea4ecbf
--- /dev/null
@@ -0,0 +1,12 @@
+Format: 1.5
+Date: 
+Source: debianqueued
+Binary: debianqueued
+Architecture: source all
+Version: 
+Distribution: unstable
+Urgency: low
+Maintainer: Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+Description: 
+ Debian Upload Queue Daemon
+Files: 
diff --git a/tools/debianqueued-0.9/config b/tools/debianqueued-0.9/config
new file mode 100644 (file)
index 0000000..6de4931
--- /dev/null
@@ -0,0 +1,142 @@
+#
+# example configuration file for debianqueued
+#
+
+# set to != 0 for debugging output (to log file)
+$debug = 0;
+
+# various programs:
+# -----------------
+$gpg       = "/usr/bin/gpg";
+$ssh       = "/usr/bin/ssh";
+$scp       = "/usr/bin/scp";
+$ssh_agent = "/usr/bin/ssh-agent";
+$ssh_add   = "/usr/bin/ssh-add";
+$md5sum    = "/usr/bin/md5sum";
+$mail      = "/usr/sbin/sendmail";
+$mkfifo    = "/usr/bin/mkfifo";
+$tar       = "/bin/tar"; # must be GNU tar!
+$gzip      = "/bin/gzip";
+$ar        = "/usr/bin/ar"; # must support p option, optional
+$ls        = "/bin/ls";
+$cp        = "/bin/cp";
+$chmod     = "/bin/chmod";
+
+# binaries which existance should be tested before each queue run
+#@test_binaries = ();
+
+# general options to ssh/scp
+$ssh_options = "-o'BatchMode yes' -o'FallBackToRsh no' ".
+               "-o'ForwardAgent no' -o'ForwardX11 no' ".
+               "-o'PasswordAuthentication no' -o'StrictHostKeyChecking yes'";
+
+# ssh key file to use for connects to master (empty: default ~/.ssh/identity)
+$ssh_key_file = "";
+
+# the incoming dir we live in
+$incoming = "/srv/queued/UploadQueue";
+
+# the delayed incoming directories
+$incoming_delayed = "/srv/queued/UploadQueue/DELAYED/%d-day";
+
+# maximum delay directory, -1 for no delayed directory,
+# incoming_delayed and target_delayed need to exist.
+$max_delayed = 15;
+
+# files not to delete in $incoming (regexp)
+$keep_files = '(status|\.message|README)$';
+
+# file patterns that aren't deleted right away
+$valid_files = '(\.changes|\.tar\.gz|\.dsc|\.u?deb|diff\.gz|\.sh)$';
+
+# Change files to mode 644 locally (after md5 check) or only on master?
+$chmod_on_target = 0;
+
+# Do an md5sum check after upload?
+$check_md5sum = 1;
+
+# name of the status file or named pipe in the incoming dir
+$statusfile = "$incoming/status";
+
+# if 0, status file implemented as FIFO; if > 0, status file is plain
+# file and updated with a delay of this many seconds
+$statusdelay = 30;
+
+# names of the keyring files
+@keyrings = ( "/srv/keyring.debian.org/keyrings/debian-keyring.gpg",
+              "/srv/keyring.debian.org/keyrings/debian-keyring.pgp",
+              "/srv/ftp.debian.org/keyrings/debian-maintainers.gpg" );
+
+# our log file
+$logfile = "$queued_dir/log";
+
+# our pid file
+$pidfile = "$queued_dir/pid";
+
+# upload method (ssh, copy, ftp)
+$upload_method = "copy";
+
+# name of target host (ignored on copy method)
+$target = "localhost";
+
+# login name on target host (for ssh, always 'ftp' for ftp, ignored for copy)
+$targetlogin = "queue";
+
+# incoming on target host
+$targetdir = "/srv/ftp.debian.org/queue/unchecked/";
+
+# incoming/delayed on target host
+$targetdir_delayed = "/srv/queued/DEFERRED/%d-day";
+
+# select FTP debugging
+#$ftpdebug = 0;
+
+# FTP timeout
+$ftptimeout = 900;
+
+# max. number of tries to upload
+$max_upload_retries = 8;
+
+# delay after first failed upload
+$upload_delay_1 = 30*60; # 30 min.
+
+# delay between successive failed uploads
+$upload_delay_2 = 4*60*60; # 4 hours
+
+# packages that must go to nonus.debian.org and thus are rejected here
+#@nonus_packages = qw(gpg-rsaidea);
+
+# timings:
+# --------
+#   time between two queue checks
+$queue_delay = 5*60; # 5 min.
+#   when are stray files deleted?
+$stray_remove_timeout = 24*60*60; # 1 day
+#   delay before reporting problems with a .changes file (not
+#   immediately for to-be-continued uploads)
+$problem_report_timeout = 30*60; # 30 min.
+#   delay before reporting that a .changes file is missing (not
+#   immediately for to-be-continued uploads)
+$no_changes_timeout = 30*60; # 30 min.
+#   when are .changes with persistent problems removed?
+$bad_changes_timeout = 2*24*60*60; # 2 days
+#   how long may a remote operation (ssh/scp) take?
+$remote_timeout = 3*60*60; # 3 hours
+
+# mail address of maintainer
+$maintainer_mail = "ftpmaster\@debian.org";
+
+
+# logfile rotating:
+# -----------------
+#    how often to rotate (in days)
+$log_age = 7;
+#    how much old logs to keep
+$log_keep = 4;
+#    send summary mail when rotating logs?
+$mail_summary = 1;
+#    write summary to file when rotating logs? (no if name empty)
+$summary_file = "$queued_dir/summary";
+
+# don't remove this, Perl needs it!
+1;
diff --git a/tools/debianqueued-0.9/config-security b/tools/debianqueued-0.9/config-security
new file mode 100644 (file)
index 0000000..0dcb7db
--- /dev/null
@@ -0,0 +1,141 @@
+#
+# example configuration file for debianqueued
+#
+
+# set to != 0 for debugging output (to log file)
+$debug = 0;
+
+# various programs:
+# -----------------
+$gpg       = "/usr/bin/gpg";
+$ssh       = "/usr/bin/ssh";
+$scp       = "/usr/bin/scp";
+$ssh_agent = "/usr/bin/ssh-agent";
+$ssh_add   = "/usr/bin/ssh-add";
+$md5sum    = "/usr/bin/md5sum";
+$mail      = "/usr/sbin/sendmail";
+$mkfifo    = "/usr/bin/mkfifo";
+$tar       = "/bin/tar"; # must be GNU tar!
+$gzip      = "/bin/gzip";
+$ar        = "/usr/bin/ar"; # must support p option, optional
+$ls        = "/bin/ls";
+$cp        = "/bin/cp";
+$chmod     = "/bin/chmod";
+
+# binaries which existance should be tested before each queue run
+#@test_binaries = ();
+
+# general options to ssh/scp
+$ssh_options = "-o'BatchMode yes' -o'FallBackToRsh no' ".
+               "-o'ForwardAgent no' -o'ForwardX11 no' ".
+               "-o'PasswordAuthentication no' -o'StrictHostKeyChecking yes'";
+
+# ssh key file to use for connects to master (empty: default ~/.ssh/identity)
+$ssh_key_file = "";
+
+# the incoming dir we live in
+$incoming = "/srv/queued/UploadQueue";
+
+# the delayed incoming directories
+$incoming_delayed = "/srv/queued/UploadQueue/DELAYED/%d-day";
+
+# maximum delay directory, -1 for no delayed directory,
+# incoming_delayed and target_delayed need to exist.
+$max_delayed = -1;
+
+# files not to delete in $incoming (regexp)
+$keep_files = '(status|\.message|README)$';
+
+# file patterns that aren't deleted right away
+$valid_files = '(\.changes|\.tar\.gz|\.dsc|\.u?deb|diff\.gz|\.sh)$';
+
+# Change files to mode 644 locally (after md5 check) or only on master?
+$chmod_on_target = 0;
+
+# Do an md5sum check?
+$check_md5sum = 0;
+
+# name of the status file or named pipe in the incoming dir
+$statusfile = "$incoming/status";
+
+# if 0, status file implemented as FIFO; if > 0, status file is plain
+# file and updated with a delay of this many seconds
+$statusdelay = 30;
+
+# names of the keyring files
+@keyrings = ( "/srv/keyring.debian.org/keyrings/debian-keyring.gpg",
+              "/srv/keyring.debian.org/keyrings/debian-keyring.pgp");
+
+# our log file
+$logfile = "$queued_dir/log";
+
+# our pid file
+$pidfile = "$queued_dir/pid";
+
+# upload method (ssh, copy, ftp)
+$upload_method = "ftp";
+
+# name of target host (ignored on copy method)
+$target = "ftp.upload.debian.org";
+
+# login name on target host (for ssh, always 'ftp' for ftp, ignored for copy)
+$targetlogin = "ftp";
+
+# incoming on target host
+$targetdir = "/pub/UploadQueue/";
+
+# incoming/delayed on target host
+$targetdir_delayed = "/srv/queued/DEFERRED/%d-day";
+
+# select FTP debugging
+$ftpdebug = 0;
+
+# FTP timeout
+$ftptimeout = 900;
+
+# max. number of tries to upload
+$max_upload_retries = 8;
+
+# delay after first failed upload
+$upload_delay_1 = 30*60; # 30 min.
+
+# delay between successive failed uploads
+$upload_delay_2 = 4*60*60; # 4 hours
+
+# packages that must go to nonus.debian.org and thus are rejected here
+#@nonus_packages = qw(gpg-rsaidea);
+
+# timings:
+# --------
+#   time between two queue checks
+$queue_delay = 5*60; # 5 min.
+#   when are stray files deleted?
+$stray_remove_timeout = 24*60*60; # 1 day
+#   delay before reporting problems with a .changes file (not
+#   immediately for to-be-continued uploads)
+$problem_report_timeout = 30*60; # 30 min.
+#   delay before reporting that a .changes file is missing (not
+#   immediately for to-be-continued uploads)
+$no_changes_timeout = 30*60; # 30 min.
+#   when are .changes with persistent problems removed?
+$bad_changes_timeout = 2*24*60*60; # 2 days
+#   how long may a remote operation (ssh/scp) take?
+$remote_timeout = 3*60*60; # 3 hours
+
+# mail address of maintainer
+$maintainer_mail = "ftpmaster\@debian.org";
+
+
+# logfile rotating:
+# -----------------
+#    how often to rotate (in days)
+$log_age = 7;
+#    how much old logs to keep
+$log_keep = 4;
+#    send summary mail when rotating logs?
+$mail_summary = 1;
+#    write summary to file when rotating logs? (no if name empty)
+$summary_file = "$queued_dir/summary";
+
+# don't remove this, Perl needs it!
+1;
diff --git a/tools/debianqueued-0.9/config-upload b/tools/debianqueued-0.9/config-upload
new file mode 100644 (file)
index 0000000..1b72e88
--- /dev/null
@@ -0,0 +1,141 @@
+#
+# example configuration file for debianqueued
+#
+
+# set to != 0 for debugging output (to log file)
+$debug = 0;
+
+# various programs:
+# -----------------
+$gpg       = "/usr/bin/gpg";
+$ssh       = "/usr/bin/ssh";
+$scp       = "/usr/bin/scp";
+$ssh_agent = "/usr/bin/ssh-agent";
+$ssh_add   = "/usr/bin/ssh-add";
+$md5sum    = "/usr/bin/md5sum";
+$mail      = "/usr/sbin/sendmail";
+$mkfifo    = "/usr/bin/mkfifo";
+$tar       = "/bin/tar"; # must be GNU tar!
+$gzip      = "/bin/gzip";
+$ar        = "/usr/bin/ar"; # must support p option, optional
+$ls        = "/bin/ls";
+$cp        = "/bin/cp";
+$chmod     = "/bin/chmod";
+
+# binaries which existance should be tested before each queue run
+#@test_binaries = ();
+
+# general options to ssh/scp
+$ssh_options = "-o'BatchMode yes' -o'FallBackToRsh no' ".
+               "-o'ForwardAgent no' -o'ForwardX11 no' ".
+               "-o'PasswordAuthentication no' -o'StrictHostKeyChecking yes'";
+
+# ssh key file to use for connects to master (empty: default ~/.ssh/identity)
+$ssh_key_file = "";
+
+# the incoming dir we live in
+$incoming = "/srv/upload.debian.org/UploadQueue";
+
+# the delayed incoming directories
+$incoming_delayed = "/srv/queued/UploadQueue/DELAYED/%d-day";
+
+# maximum delay directory, -1 for no delayed directory,
+# incoming_delayed and target_delayed need to exist.
+$max_delayed = -1;
+
+# files not to delete in $incoming (regexp)
+$keep_files = '(status|\.message|README)$';
+
+# file patterns that aren't deleted right away
+$valid_files = '(\.changes|\.tar\.gz|\.dsc|\.u?deb|diff\.gz|\.sh)$';
+
+# Change files to mode 644 locally (after md5 check) or only on master?
+$chmod_on_target = 0;
+
+# Do an md5sum check?
+$check_md5sum = 0;
+
+# name of the status file or named pipe in the incoming dir
+$statusfile = "$incoming/status";
+
+# if 0, status file implemented as FIFO; if > 0, status file is plain
+# file and updated with a delay of this many seconds
+$statusdelay = 30;
+
+# names of the keyring files
+@keyrings = ( "/srv/keyring.debian.org/keyrings/debian-keyring.gpg",
+              "/srv/keyring.debian.org/keyrings/debian-keyring.pgp");
+
+# our log file
+$logfile = "$queued_dir/log";
+
+# our pid file
+$pidfile = "$queued_dir/pid";
+
+# upload method (ssh, copy, ftp)
+$upload_method = "ftp";
+
+# name of target host (ignored on copy method)
+$target = "ftp.upload.debian.org";
+
+# login name on target host (for ssh, always 'ftp' for ftp, ignored for copy)
+$targetlogin = "ftp";
+
+# incoming on target host
+$targetdir = "/pub/UploadQueue/";
+
+# incoming/delayed on target host
+$targetdir_delayed = "/srv/queued/DEFERRED/%d-day";
+
+# select FTP debugging
+$ftpdebug = 0;
+
+# FTP timeout
+$ftptimeout = 900;
+
+# max. number of tries to upload
+$max_upload_retries = 8;
+
+# delay after first failed upload
+$upload_delay_1 = 30*60; # 30 min.
+
+# delay between successive failed uploads
+$upload_delay_2 = 4*60*60; # 4 hours
+
+# packages that must go to nonus.debian.org and thus are rejected here
+#@nonus_packages = qw(gpg-rsaidea);
+
+# timings:
+# --------
+#   time between two queue checks
+$queue_delay = 5*60; # 5 min.
+#   when are stray files deleted?
+$stray_remove_timeout = 24*60*60; # 1 day
+#   delay before reporting problems with a .changes file (not
+#   immediately for to-be-continued uploads)
+$problem_report_timeout = 30*60; # 30 min.
+#   delay before reporting that a .changes file is missing (not
+#   immediately for to-be-continued uploads)
+$no_changes_timeout = 30*60; # 30 min.
+#   when are .changes with persistent problems removed?
+$bad_changes_timeout = 2*24*60*60; # 2 days
+#   how long may a remote operation (ssh/scp) take?
+$remote_timeout = 3*60*60; # 3 hours
+
+# mail address of maintainer
+$maintainer_mail = "ftpmaster\@debian.org";
+
+
+# logfile rotating:
+# -----------------
+#    how often to rotate (in days)
+$log_age = 7;
+#    how much old logs to keep
+$log_keep = 4;
+#    send summary mail when rotating logs?
+$mail_summary = 1;
+#    write summary to file when rotating logs? (no if name empty)
+$summary_file = "$queued_dir/summary";
+
+# don't remove this, Perl needs it!
+1;
diff --git a/tools/debianqueued-0.9/debianqueued b/tools/debianqueued-0.9/debianqueued
new file mode 100755 (executable)
index 0000000..256561a
--- /dev/null
@@ -0,0 +1,2492 @@
+#!/usr/bin/perl -w
+#
+# debianqueued -- daemon for managing Debian upload queues
+#
+# Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+# Copyright (C) 2001-2007 Ryan Murray <rmurray@debian.org>
+# Copyright (C) 2008 Thomas Viehmann <tv@beamnet.de>
+#
+# This program is free software.  You can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation: either version 2 or
+# (at your option) any later version.
+# This program comes with ABSOLUTELY NO WARRANTY!
+#
+
+require 5.002;
+use strict;
+use POSIX;
+use POSIX qw( strftime sys_stat_h sys_wait_h signal_h );
+use Net::Ping;
+use Net::FTP;
+use Socket qw( PF_INET AF_INET SOCK_STREAM );
+use Config;
+
+# ---------------------------------------------------------------------------
+#                                                              configuration
+# ---------------------------------------------------------------------------
+
+package conf;
+( $conf::queued_dir = ( ( $0 !~ m,^/, ) ? POSIX::getcwd() . "/" : "" ) . $0 )
+  =~ s,/[^/]+$,,;
+require "$conf::queued_dir/config";
+my $junk = $conf::debug;    # avoid spurious warnings about unused vars
+$junk = $conf::ssh_key_file;
+$junk = $conf::stray_remove_timeout;
+$junk = $conf::problem_report_timeout;
+$junk = $conf::queue_delay;
+$junk = $conf::keep_files;
+$junk = $conf::valid_files;
+$junk = $conf::max_upload_retries;
+$junk = $conf::upload_delay_1;
+$junk = $conf::upload_delay_2;
+$junk = $conf::ar;
+$junk = $conf::gzip;
+$junk = $conf::cp;
+$junk = $conf::check_md5sum;
+
+#$junk = $conf::ls;
+$junk         = $conf::chmod;
+$junk         = $conf::ftpdebug;
+$junk         = $conf::ftptimeout;
+$junk         = $conf::no_changes_timeout;
+$junk         = @conf::nonus_packages;
+$junk         = @conf::test_binaries;
+$junk         = @conf::maintainer_mail;
+$junk         = @conf::targetdir_delayed;
+$junk         = $conf::mail ||= '/usr/sbin/sendmail';
+$conf::target = "localhost" if $conf::upload_method eq "copy";
+
+package main;
+
+( $main::progname = $0 ) =~ s,.*/,,;
+
+my %packages = ();
+
+# extract -r and -k args
+$main::arg = "";
+if ( @ARGV == 1 && $ARGV[0] =~ /^-[rk]$/ ) {
+  $main::arg = ( $ARGV[0] eq '-k' ) ? "kill" : "restart";
+  shift @ARGV;
+}
+
+# test for another instance of the queued already running
+my ( $pid, $delayed_dirs, $adelayedcore );
+if ( open( PIDFILE, "<$conf::pidfile" ) ) {
+  chomp( $pid = <PIDFILE> );
+  close(PIDFILE);
+  if ( !$pid ) {
+
+    # remove stale pid file
+    unlink($conf::pidfile);
+  } elsif ($main::arg) {
+    local ($|) = 1;
+    print "Killing running daemon (pid $pid) ...";
+    kill( 15, $pid );
+    my $cnt = 20;
+    while ( kill( 0, $pid ) && $cnt-- > 0 ) {
+      sleep 1;
+      print ".";
+    }
+    if ( kill( 0, $pid ) ) {
+      print " failed!\nProcess $pid still running.\n";
+      exit 1;
+    }
+    print "ok\n";
+    if ( -e "$conf::incoming/core" ) {
+      unlink("$conf::incoming/core");
+      print "(Removed core file)\n";
+    }
+    for ( $delayed_dirs = 0 ;
+          $delayed_dirs <= $conf::max_delayed ;
+          $delayed_dirs++ )
+    {
+      $adelayedcore =
+        sprintf( "$conf::incoming_delayed/core", $delayed_dirs );
+      if ( -e $adelayedcore ) {
+        unlink($adelayedcore);
+        print "(Removed core file)\n";
+      }
+    } ## end for ( $delayed_dirs = 0...
+    exit 0 if $main::arg eq "kill";
+  } else {
+    die "Another $main::progname is already running (pid $pid)\n"
+      if $pid && kill( 0, $pid );
+  }
+} elsif ( $main::arg eq "kill" ) {
+  die "No daemon running\n";
+} elsif ( $main::arg eq "restart" ) {
+  print "(No daemon running; starting anyway)\n";
+}
+
+# if started without arguments (initial invocation), then fork
+if ( !@ARGV ) {
+
+  # now go to background
+  die "$main::progname: fork failed: $!\n"
+    unless defined( $pid = fork );
+  if ($pid) {
+
+    # parent: wait for signal from child (SIGCHLD or SIGUSR1) and exit
+    my $sigset = POSIX::SigSet->new();
+    $sigset->emptyset();
+    $SIG{"CHLD"} = sub { };
+    $SIG{"USR1"} = sub { };
+    POSIX::sigsuspend($sigset);
+    waitpid( $pid, WNOHANG );
+    if ( kill( 0, $pid ) ) {
+      print "Daemon started in background (pid $pid)\n";
+      exit 0;
+    } else {
+      exit 1;
+    }
+  } else {
+
+    # child
+    setsid;
+    if ( $conf::upload_method eq "ssh" ) {
+
+      # exec an ssh-agent that starts us again
+      # force shell to be /bin/sh, ssh-agent may base its decision
+      # whether to use a fd or a Unix socket on the shell...
+      $ENV{"SHELL"} = "/bin/sh";
+      exec $conf::ssh_agent, $0, "startup", getppid();
+      die "$main::progname: Could not exec $conf::ssh_agent: $!\n";
+    } else {
+
+      # no need to exec, just set up @ARGV as expected below
+      @ARGV = ( "startup", getppid() );
+    }
+  } ## end else [ if ($pid)
+} ## end if ( !@ARGV )
+die "Please start without any arguments.\n"
+  if @ARGV != 2 || $ARGV[0] ne "startup";
+my $parent_pid = $ARGV[1];
+
+do {
+  my $version;
+  ( $version =
+'Release: 0.9 $Revision: 1.51 $ $Date: 1999/07/08 09:43:21 $ $Author: ftplinux $'
+  ) =~ s/\$ ?//g;
+  print "debianqueued $version\n";
+};
+
+# check if all programs exist
+my $prg;
+foreach $prg ( $conf::gpg, $conf::ssh, $conf::scp, $conf::ssh_agent,
+               $conf::ssh_add, $conf::md5sum, $conf::mail, $conf::mkfifo )
+{
+  die "Required program $prg doesn't exist or isn't executable\n"
+    if !-x $prg;
+
+  # check for correct upload method
+  die "Bad upload method '$conf::upload_method'.\n"
+    if $conf::upload_method ne "ssh"
+      && $conf::upload_method ne "ftp"
+      && $conf::upload_method ne "copy";
+  die "No keyrings\n" if !@conf::keyrings;
+
+} ## end foreach $prg ( $conf::gpg, ...
+die "statusfile path must be absolute."
+  if $conf::statusfile !~ m,^/,;
+die "upload and target queue paths must be absolute."
+  if $conf::incoming !~ m,^/,
+    || $conf::incoming_delayed !~ m,^/,
+    || $conf::targetdir !~ m,^/,
+    || $conf::targetdir_delayed !~ m,^/,;
+
+# ---------------------------------------------------------------------------
+#                                                         initializations
+# ---------------------------------------------------------------------------
+
+# prototypes
+sub calc_delta();
+sub check_dir();
+sub get_filelist_from_known_good_changes($);
+sub age_delayed_queues();
+sub process_changes($\@);
+sub process_commands($);
+sub age_delayed_queues();
+sub is_on_target($\@);
+sub copy_to_target(@);
+sub pgp_check($);
+sub check_alive(;$);
+sub check_incoming_writable();
+sub fork_statusd();
+sub write_status_file();
+sub print_status($$$$$$);
+sub format_status_num(\$$);
+sub format_status_str(\$$);
+sub send_status();
+sub ftp_open();
+sub ftp_cmd($@);
+sub ftp_close();
+sub ftp_response();
+sub ftp_code();
+sub ftp_error();
+sub ssh_cmd($);
+sub scp_cmd(@);
+sub local_cmd($;$);
+sub check_alive(;$);
+sub check_incoming_writable();
+sub rm(@);
+sub md5sum($);
+sub is_debian_file($);
+sub get_maintainer($);
+sub debian_file_stem($);
+sub msg($@);
+sub debug(@);
+sub init_mail(;$);
+sub finish_mail();
+sub send_mail($$$);
+sub try_to_get_mail_addr($$);
+sub format_time();
+sub print_time($);
+sub block_signals();
+sub unblock_signals();
+sub close_log($);
+sub kid_died($);
+sub restart_statusd();
+sub fatal_signal($);
+
+$ENV{"PATH"} = "/bin:/usr/bin";
+$ENV{"IFS"} = "" if defined( $ENV{"IFS"} && $ENV{"IFS"} ne "" );
+
+# constants for stat
+sub ST_DEV()   { 0 }
+sub ST_INO()   { 1 }
+sub ST_MODE()  { 2 }
+sub ST_NLINK() { 3 }
+sub ST_UID()   { 4 }
+sub ST_GID()   { 5 }
+sub ST_RDEV()  { 6 }
+sub ST_SIZE()  { 7 }
+sub ST_ATIME() { 8 }
+sub ST_MTIME() { 9 }
+sub ST_CTIME() { 10 }
+
+# fixed lengths of data items passed over status pipe
+sub STATNUM_LEN() { 30 }
+sub STATSTR_LEN() { 128 }
+
+# init list of signals
+defined $Config{sig_name}
+  or die "$main::progname: No signal list defined!\n";
+my $i = 0;
+my $name;
+foreach $name ( split( ' ', $Config{sig_name} ) ) {
+  $main::signo{$name} = $i++;
+}
+
+@main::fatal_signals = qw( INT QUIT ILL TRAP ABRT BUS FPE USR2 SEGV PIPE
+  TERM XCPU XFSZ PWR );
+
+$main::block_sigset = POSIX::SigSet->new;
+$main::block_sigset->addset( $main::signo{"INT"} );
+$main::block_sigset->addset( $main::signo{"TERM"} );
+
+# some constant net stuff
+$main::tcp_proto = ( getprotobyname('tcp') )[2]
+  or die "Cannot get protocol number for 'tcp'\n";
+my $used_service = ( $conf::upload_method eq "ssh" ) ? "ssh" : "ftp";
+$main::echo_port = ( getservbyname( $used_service, 'tcp' ) )[2]
+  or die "Cannot get port number for service '$used_service'\n";
+
+# clear queue of stored mails
+@main::stored_mails = ();
+
+# run ssh-add to bring the key into the agent (will use stdin/stdout)
+if ( $conf::upload_method eq "ssh" ) {
+  system "$conf::ssh_add $conf::ssh_key_file"
+    and die "$main::progname: Running $conf::ssh_add failed "
+    . "(exit status ", $? >> 8, ")\n";
+}
+
+# change to queue dir
+chdir($conf::incoming)
+  or die "$main::progname: cannot cd to $conf::incoming: $!\n";
+
+# needed before /dev/null redirects, some system send a SIGHUP when loosing
+# the controlling tty
+$SIG{"HUP"} = "IGNORE";
+
+# open logfile, make it unbuffered
+open( LOG, ">>$conf::logfile" )
+  or die "Cannot open my logfile $conf::logfile: $!\n";
+chmod( 0644, $conf::logfile )
+  or die "Cannot set modes of $conf::logfile: $!\n";
+select( ( select(LOG), $| = 1 )[0] );
+
+sleep(1);
+$SIG{"HUP"} = \&close_log;
+
+# redirect stdin, ... to /dev/null
+open( STDIN, "</dev/null" )
+  or die "$main::progname: Can't redirect stdin to /dev/null: $!\n";
+open( STDOUT, ">&LOG" )
+  or die "$main::progname: Can't redirect stdout to $conf::logfile: $!\n";
+open( STDERR, ">&LOG" )
+  or die "$main::progname: Can't redirect stderr to $conf::logfile: $!\n";
+
+# ok, from this point usually no "die" anymore, stderr is gone!
+msg( "log", "daemon (pid $$) started\n" );
+
+# initialize variables used by send_status before launching the status daemon
+$main::dstat = "i";
+format_status_num( $main::next_run, time + 10 );
+format_status_str( $main::current_changes, "" );
+check_alive();
+$main::incoming_writable = 1;    # assume this for now
+
+# start the daemon watching the 'status' FIFO
+if ( $conf::statusfile && $conf::statusdelay == 0 ) {
+  $main::statusd_pid = fork_statusd();
+  $SIG{"CHLD"}       = \&kid_died;       # watch out for dead status daemon
+                                         # SIGUSR1 triggers status info
+  $SIG{"USR1"}       = \&send_status;
+} ## end if ( $conf::statusfile...
+$main::maind_pid = $$;
+
+END {
+  kill( $main::signo{"ABRT"}, $$ )
+    if defined $main::signo{"ABRT"};
+}
+
+# write the pid file
+open( PIDFILE, ">$conf::pidfile" )
+  or msg( "log", "Can't open $conf::pidfile: $!\n" );
+printf PIDFILE "%5d\n", $$;
+close(PIDFILE);
+chmod( 0644, $conf::pidfile )
+  or die "Cannot set modes of $conf::pidfile: $!\n";
+
+# other signals will just log an error and exit
+foreach (@main::fatal_signals) {
+  $SIG{$_} = \&fatal_signal;
+}
+
+# send signal to user-started process that we're ready and it can exit
+kill( $main::signo{"USR1"}, $parent_pid );
+
+# ---------------------------------------------------------------------------
+#                                                               the mainloop
+# ---------------------------------------------------------------------------
+
+# default to classical incoming/target
+$main::current_incoming  = $conf::incoming;
+$main::current_targetdir = $conf::targetdir;
+
+$main::dstat = "i";
+write_status_file() if $conf::statusdelay;
+while (1) {
+
+  # ping target only if there is the possibility that we'll contact it (but
+  # also don't wait too long).
+  my @have_changes = <*.changes *.commands>;
+  for ( my $delayed_dirs = 0 ;
+        $delayed_dirs <= $conf::max_delayed ;
+        $delayed_dirs++ )
+  {
+    my $adelayeddir = sprintf( "$conf::incoming_delayed", $delayed_dirs );
+    push( @have_changes, <$adelayeddir/*.changes> );
+  } ## end for ( my $delayed_dirs ...
+  check_alive()
+    if @have_changes || ( time - $main::last_ping_time ) > 8 * 60 * 60;
+
+  if ( @have_changes && $main::target_up ) {
+    check_incoming_writable if !$main::incoming_writable;
+    check_dir() if $main::incoming_writable;
+  }
+  $main::dstat = "i";
+  write_status_file() if $conf::statusdelay;
+
+  if ( $conf::upload_method eq "copy" ) {
+    age_delayed_queues();
+  }
+
+  # sleep() returns if we received a signal (SIGUSR1 for status FIFO), so
+  # calculate the end time once and wait for it being reached.
+  format_status_num( $main::next_run, time + $conf::queue_delay );
+  my $delta;
+  while ( ( $delta = calc_delta() ) > 0 ) {
+    debug("mainloop sleeping $delta secs");
+    sleep($delta);
+
+    # check if statusd died, if using status FIFO, or update status file
+    if ($conf::statusdelay) {
+      write_status_file();
+    } else {
+      restart_statusd();
+    }
+  } ## end while ( ( $delta = calc_delta...
+} ## end while (1)
+
+sub calc_delta() {
+  my $delta;
+
+  $delta = $main::next_run - time;
+  $delta = $conf::statusdelay
+    if $conf::statusdelay && $conf::statusdelay < $delta;
+  return $delta;
+} ## end sub calc_delta()
+
+# ---------------------------------------------------------------------------
+#                                                      main working functions
+# ---------------------------------------------------------------------------
+
+#
+# main function for checking the incoming dir
+#
+sub check_dir() {
+  my ( @files, @changes, @keep_files, @this_keep_files, @stats, $file,
+       $adelay );
+
+  debug("starting checkdir");
+  $main::dstat = "c";
+  write_status_file() if $conf::statusdelay;
+
+  # test if needed binaries are available; this is if they're on maybe
+  # slow-mounted NFS filesystems
+  foreach (@conf::test_binaries) {
+    next if -f $_;
+
+    # maybe the mount succeeds now
+    sleep 5;
+    next if -f $_;
+    msg( "log", "binary test failed for $_; delaying queue run\n" );
+    goto end_run;
+  } ## end foreach (@conf::test_binaries)
+
+  for ( $adelay = -1 ; $adelay <= $conf::max_delayed ; $adelay++ ) {
+    if ( $adelay == -1 ) {
+      $main::current_incoming       = $conf::incoming;
+      $main::current_incoming_short = "";
+      $main::current_targetdir      = $conf::targetdir;
+    } else {
+      $main::current_incoming = sprintf( $conf::incoming_delayed, $adelay );
+      $main::current_incoming_short = sprintf( "DELAYED/%d-day", $adelay );
+      $main::current_targetdir = sprintf( $conf::targetdir_delayed, $adelay );
+    }
+
+    # need to clear directory specific variables
+    undef(@keep_files);
+    undef(@this_keep_files);
+
+    chdir($main::current_incoming)
+      or (
+           msg(
+                "log",
+                "Cannot change to dir "
+                  . "${main::current_incoming_short}: $!\n"
+              ),
+           return
+         );
+
+    # look for *.commands files but not in delayed queues
+    if ( $adelay == -1 ) {
+      foreach $file (<*.commands>) {
+        init_mail($file);
+        block_signals();
+        process_commands($file);
+        unblock_signals();
+        $main::dstat = "c";
+        write_status_file() if $conf::statusdelay;
+        finish_mail();
+      } ## end foreach $file (<*.commands>)
+    } ## end if ( $adelay == -1 )
+    opendir( INC, "." )
+      or (
+           msg(
+                "log", "Cannot open dir ${main::current_incoming_short}: $!\n"
+              ),
+           return
+         );
+    @files = readdir(INC);
+    closedir(INC);
+
+    # process all .changes files found
+    @changes = grep /\.changes$/, @files;
+    push( @keep_files, @changes );    # .changes files aren't stray
+    foreach $file (@changes) {
+      init_mail($file);
+
+      # wrap in an eval to allow jumpbacks to here with die in case
+      # of errors
+      block_signals();
+      eval { process_changes( $file, @this_keep_files ); };
+      unblock_signals();
+      msg( "log,mail", $@ ) if $@;
+      $main::dstat = "c";
+      write_status_file() if $conf::statusdelay;
+
+      # files which are ok in conjunction with this .changes
+      debug("$file tells to keep @this_keep_files");
+      push( @keep_files, @this_keep_files );
+      finish_mail();
+
+      # break out of this loop if the incoming dir has become unwritable
+      goto end_run if !$main::incoming_writable;
+    } ## end foreach $file (@changes)
+    ftp_close() if $conf::upload_method eq "ftp";
+
+    # find files which aren't related to any .changes
+    foreach $file (@files) {
+
+      # filter out files we never want to delete
+      next if !-f $file ||    # may have disappeared in the meantime
+             $file eq "."
+          || $file eq ".."
+          || ( grep { $_ eq $file } @keep_files )
+          || $file =~ /$conf::keep_files/;
+
+      # Delete such files if they're older than
+      # $stray_remove_timeout; they could be part of an
+      # yet-incomplete upload, with the .changes still missing.
+      # Cannot send any notification, since owner unknown.
+      next if !( @stats = stat($file) );
+      my $age = time - $stats[ST_MTIME];
+      my ( $maint, $pattern, @job_files );
+      if (    $file =~ /^junk-for-writable-test/
+           || $file !~ m,$conf::valid_files,
+           || $age >= $conf::stray_remove_timeout )
+      {
+        msg( "log",
+             "Deleted stray file ${main::current_incoming_short}/$file\n" )
+          if rm($file);
+      } elsif (
+        $age > $conf::no_changes_timeout
+        && is_debian_file($file)
+        &&
+
+        # not already reported
+          !( $stats[ST_MODE] & S_ISGID )
+        && ( $pattern   = debian_file_stem($file) )
+        && ( @job_files = glob($pattern) )
+        &&
+
+        # If a .changes is in the list, it has the same stem as the
+        # found file (probably a .orig.tar.gz). Don't report in this
+        # case.
+        !( grep( /\.changes$/, @job_files ) )
+              )
+      {
+        $maint = get_maintainer($file);
+
+        # Don't send a mail if this looks like the recompilation of a
+        # package for a non-i386 arch. For those, the maintainer field is
+        # useless :-(
+        if ( !grep( /(\.dsc|_(i386|all)\.deb)$/, @job_files ) ) {
+          msg( "log", "Found an upload without .changes and with no ",
+               ".dsc file\n" );
+          msg( "log",
+               "Not sending a report, because probably ",
+               "recompilation job\n" );
+        } elsif ($maint) {
+          init_mail();
+          $main::mail_addr = $maint;
+          $main::mail_addr = $1 if $main::mail_addr =~ /<([^>]*)>/;
+          $main::mail_subject =
+            "Incomplete upload found in " . "Debian upload queue";
+          msg(
+               "mail",
+               "Probably you are the uploader of the following "
+                 . "file(s) in\n"
+             );
+          msg( "mail", "the Debian upload queue directory:\n  " );
+          msg( "mail", join( "\n  ", @job_files ), "\n" );
+          msg(
+               "mail",
+               "This looks like an upload, but a .changes file "
+                 . "is missing, so the job\n"
+             );
+          msg( "mail", "cannot be processed.\n\n" );
+          msg(
+               "mail",
+               "If no .changes file arrives within ",
+               print_time( $conf::stray_remove_timeout - $age ),
+               ", the files will be deleted.\n\n"
+             );
+          msg(
+               "mail",
+               "If you didn't upload those files, please just "
+                 . "ignore this message.\n"
+             );
+          finish_mail();
+          msg(
+               "log",
+               "Sending problem report for an upload without a "
+                 . ".changes\n"
+             );
+          msg( "log", "Maintainer: $maint\n" );
+        } else {
+          msg(
+               "log",
+               "Found an upload without .changes, but can't "
+                 . "find a maintainer address\n"
+             );
+        } ## end else [ if ( !grep( /(\.dsc|_(i386|all)\.deb)$/...
+        msg( "log", "Files: @job_files\n" );
+
+        # remember we already have sent a mail regarding this file
+        foreach (@job_files) {
+          my @st = stat($_);
+          next if !@st;    # file may have disappeared in the meantime
+          chmod +( $st[ST_MODE] |= S_ISGID ), $_;
+        }
+      } else {
+        debug(
+"found stray file ${main::current_incoming_short}/$file, deleting in ",
+          print_time( $conf::stray_remove_timeout - $age )
+        );
+      } ## end else [ if ( $file =~ /^junk-for-writable-test/...
+    } ## end foreach $file (@files)
+  } ## end for ( $adelay = -1 ; $adelay...
+  chdir($conf::incoming);
+
+end_run:
+  $main::dstat = "i";
+  write_status_file() if $conf::statusdelay;
+} ## end sub check_dir()
+
+sub get_filelist_from_known_good_changes($) {
+  my $changes = shift;
+
+  local (*CHANGES);
+  my (@filenames);
+
+  # parse the .changes file
+  open( CHANGES, "<$changes" )
+    or die "$changes: $!\n";
+outer_loop: while (<CHANGES>) {
+    if (/^Files:/i) {
+      while (<CHANGES>) {
+        redo outer_loop if !/^\s/;
+        my @field = split(/\s+/);
+        next if @field != 6;
+
+        # forbid shell meta chars in the name, we pass it to a
+        # subshell several times...
+        $field[5] =~ /^([a-zA-Z0-9.+_:@=%-][~a-zA-Z0-9.+_:@=%-]*)/;
+        if ( $1 ne $field[5] ) {
+          msg( "log", "found suspicious filename $field[5]\n" );
+          next;
+        }
+        push( @filenames, $field[5] );
+      } ## end while (<CHANGES>)
+    } ## end if (/^Files:/i)
+  } ## end while (<CHANGES>)
+  close(CHANGES);
+  return @filenames;
+} ## end sub get_filelist_from_known_good_changes($)
+
+#
+# process one .changes file
+#
+sub process_changes($\@) {
+  my $changes   = shift;
+  my $keep_list = shift;
+  my (
+       $pgplines,     @files,     @filenames,  @changes_stats,
+       $failure_file, $retries,   $last_retry, $upload_time,
+       $file,         $do_report, $ls_l,       $problems_reported,
+       $errs,         $pkgname,   $signator
+     );
+  local (*CHANGES);
+  local (*FAILS);
+
+  format_status_str( $main::current_changes,
+                     "$main::current_incoming_short/$changes" );
+  $main::dstat = "c";
+  write_status_file() if $conf::statusdelay;
+
+  @$keep_list = ();
+  msg( "log", "processing ${main::current_incoming_short}/$changes\n" );
+
+  # parse the .changes file
+  open( CHANGES, "<$changes" )
+    or die "Cannot open ${main::current_incoming_short}/$changes: $!\n";
+  $pgplines        = 0;
+  $main::mail_addr = "";
+  @files           = ();
+outer_loop: while (<CHANGES>) {
+    if (/^---+(BEGIN|END) PGP .*---+$/) {
+      ++$pgplines;
+    } elsif (/^Maintainer:\s*/i) {
+      chomp( $main::mail_addr = $' );
+      $main::mail_addr = $1 if $main::mail_addr =~ /<([^>]*)>/;
+    } elsif (/^Source:\s*/i) {
+      chomp( $pkgname = $' );
+      $pkgname =~ s/\s+$//;
+      $main::packages{$pkgname}++;
+    } elsif (/^Files:/i) {
+      while (<CHANGES>) {
+        redo outer_loop if !/^\s/;
+        my @field = split(/\s+/);
+        next if @field != 6;
+
+        # forbid shell meta chars in the name, we pass it to a
+        # subshell several times...
+        $field[5] =~ /^([a-zA-Z0-9.+_:@=%-][~a-zA-Z0-9.+_:@=%-]*)/;
+        if ( $1 ne $field[5] ) {
+          msg( "log", "found suspicious filename $field[5]\n" );
+          msg(
+            "mail",
+"File '$field[5]' mentioned in $main::current_incoming_short/$changes\n",
+            "has bad characters in its name. Removed.\n"
+          );
+          rm( $field[5] );
+          next;
+        } ## end if ( $1 ne $field[5] )
+        push(
+              @files,
+              {
+                md5  => $field[1],
+                size => $field[2],
+                name => $field[5]
+              }
+            );
+        push( @filenames, $field[5] );
+        debug( "includes file $field[5], size $field[2], ", "md5 $field[1]" );
+      } ## end while (<CHANGES>)
+    } ## end elsif (/^Files:/i)
+  } ## end while (<CHANGES>)
+  close(CHANGES);
+
+  # tell check_dir that the files mentioned in this .changes aren't stray,
+  # we know about them somehow
+  @$keep_list = @filenames;
+
+  # some consistency checks
+  if ( !$main::mail_addr ) {
+    msg( "log,mail",
+"$main::current_incoming_short/$changes doesn't contain a Maintainer: field; "
+        . "cannot process\n" );
+    goto remove_only_changes;
+  } ## end if ( !$main::mail_addr)
+  if ( $main::mail_addr !~ /^(buildd_\S+-\S+|\S+\@\S+\.\S+)/ ) {
+
+    # doesn't look like a mail address, maybe only the name
+    my ( $new_addr, @addr_list );
+    if ( $new_addr = try_to_get_mail_addr( $main::mail_addr, \@addr_list ) ) {
+
+      # substitute (unique) found addr, but give a warning
+      msg(
+           "mail",
+           "(The Maintainer: field didn't contain a proper "
+             . "mail address.\n"
+         );
+      msg(
+           "mail",
+           "Looking for `$main::mail_addr' in the Debian "
+             . "keyring gave your address\n"
+         );
+      msg( "mail", "as unique result, so I used this.)\n" );
+      msg( "log",
+           "Substituted $new_addr for malformed " . "$main::mail_addr\n" );
+      $main::mail_addr = $new_addr;
+    } else {
+
+      # not found or not unique: hold the job and inform queue maintainer
+      my $old_addr = $main::mail_addr;
+      $main::mail_addr = $conf::maintainer_mail;
+      msg(
+        "mail",
+"The job ${main::current_incoming_short}/$changes doesn't have a correct email\n"
+      );
+      msg( "mail", "address in the Maintainer: field:\n" );
+      msg( "mail", "  $old_addr\n" );
+      msg( "mail", "A check for this in the Debian keyring gave:\n" );
+      msg( "mail",
+           @addr_list
+           ? "  " . join( ", ", @addr_list ) . "\n"
+           : "  nothing\n" );
+      msg( "mail", "Please fix this manually\n" );
+      msg(
+        "log",
+"Bad Maintainer: field in ${main::current_incoming_short}/$changes: $old_addr\n"
+      );
+      goto remove_only_changes;
+    } ## end else [ if ( $new_addr = try_to_get_mail_addr...
+  } ## end if ( $main::mail_addr ...
+  if ( $pgplines < 3 ) {
+    msg(
+        "log,mail",
+        "$main::current_incoming_short/$changes isn't signed with PGP/GnuPG\n"
+       );
+    msg( "log", "(uploader $main::mail_addr)\n" );
+    goto remove_only_changes;
+  } ## end if ( $pgplines < 3 )
+  if ( !@files ) {
+    msg( "log,mail",
+       "$main::current_incoming_short/$changes doesn't mention any files\n" );
+    msg( "log", "(uploader $main::mail_addr)\n" );
+    goto remove_only_changes;
+  } ## end if ( !@files )
+
+  # check for packages that shouldn't be processed
+  if ( grep( $_ eq $pkgname, @conf::nonus_packages ) ) {
+    msg(
+         "log,mail",
+         "$pkgname is a package that must be uploaded "
+           . "to nonus.debian.org\n"
+       );
+    msg( "log,mail", "instead of target.\n" );
+    msg( "log,mail",
+         "Job rejected and removed all files belonging " . "to it:\n" );
+    msg( "log,mail", "  ", join( ", ", @filenames ), "\n" );
+    rm( $changes, @filenames );
+    return;
+  } ## end if ( grep( $_ eq $pkgname...
+
+  $failure_file = $changes . ".failures";
+  $retries = $last_retry = 0;
+  if ( -f $failure_file ) {
+    open( FAILS, "<$failure_file" )
+      or die "Cannot open $main::current_incoming_short/$failure_file: $!\n";
+    my $line = <FAILS>;
+    close(FAILS);
+    ( $retries, $last_retry ) = ( $1, $2 )
+      if $line =~ /^(\d+)\s+(\d+)$/;
+    push( @$keep_list, $failure_file );
+  } ## end if ( -f $failure_file )
+
+  # run PGP on the file to check the signature
+  if ( !( $signator = pgp_check($changes) ) ) {
+    msg(
+       "log,mail",
+       "$main::current_incoming_short/$changes has bad PGP/GnuPG signature!\n"
+    );
+    msg( "log", "(uploader $main::mail_addr)\n" );
+  remove_only_changes:
+    msg(
+      "log,mail",
+"Removing $main::current_incoming_short/$changes, but keeping its associated ",
+      "files for now.\n"
+    );
+    rm($changes);
+
+    # Set SGID bit on associated files, so that the test for Debian files
+    # without a .changes doesn't consider them.
+    foreach (@filenames) {
+      my @st = stat($_);
+      next if !@st;    # file may have disappeared in the meantime
+      chmod +( $st[ST_MODE] |= S_ISGID ), $_;
+    }
+    return;
+  } elsif ( $signator eq "LOCAL ERROR" ) {
+
+    # An error has appened when starting pgp... Don't process the file,
+    # but also don't delete it
+    debug(
+"Can't PGP/GnuPG check $main::current_incoming_short/$changes -- don't process it for now"
+    );
+    return;
+  } ## end elsif ( $signator eq "LOCAL ERROR")
+
+  die "Cannot stat ${main::current_incoming_short}/$changes (??): $!\n"
+    if !( @changes_stats = stat($changes) );
+
+  # Make $upload_time the maximum of all modification times of files
+  # related to this .changes (and the .changes it self). This is the
+  # last time something changes to these files.
+  $upload_time = $changes_stats[ST_MTIME];
+  for $file (@files) {
+    my @stats;
+    next if !( @stats = stat( $file->{"name"} ) );
+    $file->{"stats"} = \@stats;
+    $upload_time = $stats[ST_MTIME] if $stats[ST_MTIME] > $upload_time;
+  } ## end for $file (@files)
+
+  $do_report = ( time - $upload_time ) > $conf::problem_report_timeout;
+  $problems_reported = $changes_stats[ST_MODE] & S_ISGID;
+
+  # if any of the files is newer than the .changes' ctime (the time
+  # we sent a report and set the sticky bit), send new problem reports
+  if ( $problems_reported && $changes_stats[ST_CTIME] < $upload_time ) {
+    $problems_reported = 0;
+    chmod +( $changes_stats[ST_MODE] &= ~S_ISGID ), $changes;
+    debug("upload_time>changes-ctime => resetting problems reported");
+  }
+  debug("do_report=$do_report problems_reported=$problems_reported");
+
+  # now check all files for correct size and md5 sum
+  for $file (@files) {
+    my $filename = $file->{"name"};
+    if ( !defined( $file->{"stats"} ) ) {
+
+      # could be an upload that isn't complete yet, be quiet,
+      # but don't process the file;
+      msg( "log,mail", "$filename doesn't exist\n" )
+        if $do_report && !$problems_reported;
+      msg( "log", "$filename doesn't exist (ignored for now)\n" )
+        if !$do_report;
+      msg( "log", "$filename doesn't exist (already reported)\n" )
+        if $problems_reported;
+      ++$errs;
+    } elsif ( $file->{"stats"}->[ST_SIZE] < $file->{"size"}
+              && !$do_report )
+    {
+
+      # could be an upload that isn't complete yet, be quiet,
+      # but don't process the file
+      msg( "log", "$filename is too small (ignored for now)\n" );
+      ++$errs;
+    } elsif ( $file->{"stats"}->[ST_SIZE] != $file->{"size"} ) {
+      msg( "log,mail", "$filename has incorrect size; deleting it\n" );
+      rm($filename);
+      ++$errs;
+    } elsif ( md5sum($filename) ne $file->{"md5"} ) {
+      msg( "log,mail",
+           "$filename has incorrect md5 checksum; ",
+           "deleting it\n" );
+      rm($filename);
+      ++$errs;
+    } ## end elsif ( md5sum($filename)...
+  } ## end for $file (@files)
+
+  if ($errs) {
+    if ( ( time - $upload_time ) > $conf::bad_changes_timeout ) {
+
+      # if a .changes fails for a really long time (several days
+      # or so), remove it and all associated files
+      msg(
+          "log,mail",
+          "$main::current_incoming_short/$changes couldn't be processed for ",
+          int( $conf::bad_changes_timeout / ( 60 * 60 ) ),
+          " hours and is now deleted\n"
+         );
+      msg( "log,mail", "All files it mentions are also removed:\n" );
+      msg( "log,mail", "  ", join( ", ", @filenames ), "\n" );
+      rm( $changes, @filenames, $failure_file );
+    } elsif ( $do_report && !$problems_reported ) {
+
+      # otherwise, send a problem report, if not done already
+      msg(
+           "mail",
+           "Due to the errors above, the .changes file couldn't ",
+           "be processed.\n",
+           "Please fix the problems for the upload to happen.\n"
+         );
+
+      # remember we already have sent a mail regarding this file
+      debug("Sending problem report mail and setting SGID bit");
+      my $mode = $changes_stats[ST_MODE] |= S_ISGID;
+      msg( "log", "chmod failed: $!" )
+        if ( chmod( $mode, $changes ) != 1 );
+    } ## end elsif ( $do_report && !$problems_reported)
+
+    # else: be quiet
+
+    return;
+  } ## end if ($errs)
+
+  # if this upload already failed earlier, wait until the delay requirement
+  # is fulfilled
+  if ( $retries > 0
+       && ( time - $last_retry ) <
+       ( $retries == 1 ? $conf::upload_delay_1 : $conf::upload_delay_2 ) )
+  {
+    msg( "log", "delaying retry of upload\n" );
+    return;
+  } ## end if ( $retries > 0 && (...
+
+  if ( $conf::upload_method eq "ftp" ) {
+    return if !ftp_open();
+  }
+
+  # check if the job is already present on target
+  # (moved to here, to avoid bothering target as long as there are errors in
+  # the job)
+  if ( $ls_l = is_on_target( $changes, @filenames ) ) {
+    msg(
+      "log,mail",
+"$main::current_incoming_short/$changes is already present on target host:\n"
+    );
+    msg( "log,mail", "$ls_l\n" );
+    msg( "mail",
+         "Either you already uploaded it, or someone else ",
+         "came first.\n" );
+    msg( "log,mail", "Job $changes removed.\n" );
+    rm( $changes, @filenames, $failure_file );
+    return;
+  } ## end if ( $ls_l = is_on_target...
+
+  # clear sgid bit before upload, scp would copy it to target. We don't need
+  # it anymore, we know there are no problems if we come here. Also change
+  # mode of files to 644 if this should be done locally.
+  $changes_stats[ST_MODE] &= ~S_ISGID;
+  if ( !$conf::chmod_on_target ) {
+    $changes_stats[ST_MODE] &= ~0777;
+    $changes_stats[ST_MODE] |= 0644;
+  }
+  chmod +( $changes_stats[ST_MODE] ), $changes;
+
+  # try uploading to target
+  if ( !copy_to_target( $changes, @filenames ) ) {
+
+    # if the upload failed, increment the retry counter and remember the
+    # current time; both things are written to the .failures file. Don't
+    # increment the fail counter if the error was due to incoming
+    # unwritable.
+    return if !$main::incoming_writable;
+    if ( ++$retries >= $conf::max_upload_retries ) {
+      msg( "log,mail",
+           "$changes couldn't be uploaded for $retries times now.\n" );
+      msg( "log,mail",
+           "Giving up and removing it and its associated files:\n" );
+      msg( "log,mail", "  ", join( ", ", @filenames ), "\n" );
+      rm( $changes, @filenames, $failure_file );
+    } else {
+      $last_retry = time;
+      if ( open( FAILS, ">$failure_file" ) ) {
+        print FAILS "$retries $last_retry\n";
+        close(FAILS);
+        chmod( 0600, $failure_file )
+          or die "Cannot set modes of $failure_file: $!\n";
+      } ## end if ( open( FAILS, ">$failure_file"...
+      push( @$keep_list, $failure_file );
+      debug("now $retries failed uploads");
+      msg(
+           "mail",
+           "The upload will be retried in ",
+           print_time(
+                         $retries == 1
+                       ? $conf::upload_delay_1
+                       : $conf::upload_delay_2
+                     ),
+           "\n"
+         );
+    } ## end else [ if ( ++$retries >= $conf::max_upload_retries)
+    return;
+  } ## end if ( !copy_to_target( ...
+
+  # If the files were uploaded ok, remove them
+  rm( $changes, @filenames, $failure_file );
+
+  msg( "mail", "$changes uploaded successfully to $conf::target\n" );
+  msg( "mail", "along with the files:\n  ", join( "\n  ", @filenames ),
+       "\n" );
+  msg( "log",
+       "$changes processed successfully (uploader $main::mail_addr)\n" );
+
+  # Check for files that have the same stem as the .changes (and weren't
+  # mentioned there) and delete them. It happens often enough that people
+  # upload a .orig.tar.gz where it isn't needed and also not in the
+  # .changes. Explicitly deleting it (and not waiting for the
+  # $stray_remove_timeout) reduces clutter in the queue dir and maybe also
+  # educates uploaders :-)
+
+  #    my $pattern = debian_file_stem( $changes );
+  #    my $spattern = substr( $pattern, 0, -1 ); # strip off '*' at end
+  #    my @other_files = glob($pattern);
+  # filter out files that have a Debian revision at all and a different
+  # revision. Those belong to a different upload.
+  #    if ($changes =~ /^\Q$spattern\E-([\d.+-]+)/) {
+  #            my $this_rev = $1;
+  #            @other_files = grep( !/^\Q$spattern\E-([\d.+-]+)/ || $1 eq $this_rev,
+  #                                                     @other_files);
+  #}
+  # Also do not remove those files if a .changes is among them. Then there
+  # is probably a second upload for another version or another architecture.
+  #    if (@other_files && !grep( /\.changes$/, @other_files )) {
+  #            rm( @other_files );
+  #            msg( "mail", "\nThe following file(s) seemed to belong to the same ".
+  #                                     "upload, but weren't listed\n" );
+  #            msg( "mail", "in the .changes file:\n  " );
+  #            msg( "mail", join( "\n  ", @other_files ), "\n" );
+  #            msg( "mail", "They have been deleted.\n" );
+  #            msg( "log", "Deleted files in upload not in $changes: @other_files\n" );
+  #}
+} ## end sub process_changes($\@)
+
+#
+# process one .commands file
+#
+sub process_commands($) {
+  my $commands = shift;
+  my ( @cmds, $cmd, $pgplines, $signator );
+  local (*COMMANDS);
+  my ( @files, $file, @removed, $target_delay );
+
+  format_status_str( $main::current_changes, $commands );
+  $main::dstat = "c";
+  write_status_file() if $conf::statusdelay;
+
+  msg( "log", "processing $main::current_incoming_short/$commands\n" );
+
+  # parse the .commands file
+  if ( !open( COMMANDS, "<$commands" ) ) {
+    msg( "log", "Cannot open $main::current_incoming_short/$commands: $!\n" );
+    return;
+  }
+  $pgplines        = 0;
+  $main::mail_addr = "";
+  @cmds            = ();
+outer_loop: while (<COMMANDS>) {
+    if (/^---+(BEGIN|END) PGP .*---+$/) {
+      ++$pgplines;
+    } elsif (/^Uploader:\s*/i) {
+      chomp( $main::mail_addr = $' );
+      $main::mail_addr = $1 if $main::mail_addr =~ /<([^>]*)>/;
+    } elsif (/^Commands:/i) {
+      $_ = $';
+      for ( ; ; ) {
+        s/^\s*(.*)\s*$/$1/;    # delete whitespace at both ends
+        if ( !/^\s*$/ ) {
+          push( @cmds, $_ );
+          debug("includes cmd $_");
+        }
+        last outer_loop if !defined( $_ = scalar(<COMMANDS>) );
+        chomp;
+        redo outer_loop if !/^\s/ || /^$/;
+      } ## end for ( ; ; )
+    } ## end elsif (/^Commands:/i)
+  } ## end while (<COMMANDS>)
+  close(COMMANDS);
+
+  # some consistency checks
+  if ( !$main::mail_addr || $main::mail_addr !~ /^\S+\@\S+\.\S+/ ) {
+    msg( "log,mail",
+"$main::current_incoming_short/$commands contains no or bad Uploader: field: "
+        . "$main::mail_addr\n" );
+    msg( "log,mail",
+         "cannot process $main::current_incoming_short/$commands\n" );
+    $main::mail_addr = "";
+    goto remove;
+  } ## end if ( !$main::mail_addr...
+  msg( "log", "(command uploader $main::mail_addr)\n" );
+
+  if ( $pgplines < 3 ) {
+    msg(
+       "log,mail",
+       "$main::current_incoming_short/$commands isn't signed with PGP/GnuPG\n"
+    );
+    msg(
+      "mail",
+      "or the uploaded file is broken. Make sure to transfer in binary mode\n"
+    );
+    msg( "mail", "or better yet - use dcut for commands files\n" );
+    goto remove;
+  } ## end if ( $pgplines < 3 )
+
+  # run PGP on the file to check the signature
+  if ( !( $signator = pgp_check($commands) ) ) {
+    msg(
+      "log,mail",
+      "$main::current_incoming_short/$commands has bad PGP/GnuPG signature!\n"
+    );
+  remove:
+    msg( "log,mail", "Removing $main::current_incoming_short/$commands\n" );
+    rm($commands);
+    return;
+  } elsif ( $signator eq "LOCAL ERROR" ) {
+
+    # An error has appened when starting pgp... Don't process the file,
+    # but also don't delete it
+    debug(
+"Can't PGP/GnuPG check $main::current_incoming_short/$commands -- don't process it for now"
+    );
+    return;
+  } ## end elsif ( $signator eq "LOCAL ERROR")
+  msg( "log", "(PGP/GnuPG signature by $signator)\n" );
+
+  # now process commands
+  msg(
+    "mail",
+"Log of processing your commands file $main::current_incoming_short/$commands:\n\n"
+  );
+  foreach $cmd (@cmds) {
+    my @word = split( /\s+/, $cmd );
+    msg( "mail,log", "> @word\n" );
+    my $selecteddelayed = -1;
+    next if @word < 1;
+
+    if ( $word[0] eq "rm" ) {
+      foreach ( @word[ 1 .. $#word ] ) {
+        my $origword = $_;
+        if (m,^DELAYED/([0-9]+)-day/,) {
+          $selecteddelayed = $1;
+          s,^DELAYED/[0-9]+-day/,,;
+        }
+        if ( $origword eq "--searchdirs" ) {
+          $selecteddelayed = -2;
+        } elsif (m,/,) {
+          msg(
+            "mail,log",
+"$_: filename may not contain slashes except for DELAYED/#-day/ prefixes\n"
+          );
+        } else {
+
+          # process wildcards but also plain names
+          my (@thesefiles);
+          my $pat = quotemeta($_);
+          $pat =~ s/\\\*/.*/g;
+          $pat =~ s/\\\?/.?/g;
+          $pat =~ s/\\([][])/$1/g;
+
+          if ( $selecteddelayed < 0 ) {    # scanning or explicitly incoming
+            opendir( DIR, "." );
+            push( @thesefiles, grep /^$pat$/, readdir(DIR) );
+            closedir(DIR);
+          }
+          if ( $selecteddelayed >= 0 ) {
+            my $dir = sprintf( $conf::incoming_delayed, $selecteddelayed );
+            opendir( DIR, $dir );
+            push( @thesefiles,
+                  map ( "$dir/$_", grep /^$pat$/, readdir(DIR) ) );
+            closedir(DIR);
+          } elsif ( $selecteddelayed == -2 ) {
+            for ( my ($adelay) = 0 ;
+                  ( !@thesefiles ) && $adelay <= $conf::max_delayed ;
+                  $adelay++ )
+            {
+              my $dir = sprintf( $conf::incoming_delayed, $adelay );
+              opendir( DIR, $dir );
+              push( @thesefiles,
+                    map ( "$dir/$_", grep /^$pat$/, readdir(DIR) ) );
+              closedir(DIR);
+            } ## end for ( my ($adelay) = 0 ...
+          } ## end elsif ( $selecteddelayed ...
+          push( @files, @thesefiles );
+          if ( !@thesefiles ) {
+            msg( "mail,log", "$origword did not match anything\n" );
+          }
+        } ## end else [ if ( $origword eq "--searchdirs")
+      } ## end foreach ( @word[ 1 .. $#word...
+      if ( !@files ) {
+        msg( "mail,log", "No files to delete\n" );
+      } else {
+        @removed = ();
+        foreach $file (@files) {
+          if ( !-f $file ) {
+            msg( "mail,log", "$file: no such file\n" );
+          } elsif ( $file =~ /$conf::keep_files/ ) {
+            msg( "mail,log", "$file is protected, cannot " . "remove\n" );
+          } elsif ( !unlink($file) ) {
+            msg( "mail,log", "$file: rm: $!\n" );
+          } else {
+            $file =~ s,$conf::incoming/?,,;
+            push( @removed, $file );
+          }
+        } ## end foreach $file (@files)
+        msg( "mail,log", "Files removed: @removed\n" ) if @removed;
+      } ## end else [ if ( !@files )
+    } elsif ( $word[0] eq "reschedule" ) {
+      if ( @word != 3 ) {
+        msg( "mail,log", "Wrong number of arguments\n" );
+      } elsif ( $conf::upload_method ne "copy" ) {
+        msg( "mail,log", "reschedule not available\n" );
+      } elsif ( $word[1] =~ m,/, || $word[1] !~ m/\.changes/ ) {
+        msg(
+           "mail,log",
+           "$word[1]: filename may not contain slashes and must be .changes\n"
+        );
+      } elsif ( !( ($target_delay) = $word[2] =~ m,^([0-9]+)-day$, )
+                || $target_delay > $conf::max_delayed )
+      {
+        msg(
+          "mail,log",
+"$word[2]: rescheduling target must be #-day with # between 0 and $conf::max_delayed (in particular, no '/' allowed)\n"
+        );
+      } elsif ( $word[1] =~ /$conf::keep_files/ ) {
+        msg( "mail,log", "$word[1] is protected, cannot do stuff with it\n" );
+      } else {
+        my ($adelay);
+        for ( $adelay = 0 ;
+            $adelay <= $conf::max_delayed
+            && !-f (
+              sprintf( "$conf::targetdir_delayed", $adelay ) . "/$word[1]" ) ;
+            $adelay++ )
+        {
+        } ## end for ( $adelay = 0 ; $adelay...
+        if ( $adelay > $conf::max_delayed ) {
+          msg( "mail,log", "$word[1] not found\n" );
+        } elsif ( $adelay == $target_delay ) {
+          msg( "mail,log", "$word[1] already is in $word[2]\n" );
+        } else {
+          my (@thesefiles);
+          my ($dir) = sprintf( "$conf::targetdir_delayed", $adelay );
+          my ($target_dir) =
+            sprintf( "$conf::targetdir_delayed", $target_delay );
+          push( @thesefiles, $word[1] );
+          push( @thesefiles,
+                get_filelist_from_known_good_changes("$dir/$word[1]") );
+          for my $afile (@thesefiles) {
+            if ( $afile =~ m/\.changes$/ ) {
+              utime undef, undef, ("$dir/$afile");
+            }
+            if ( !rename "$dir/$afile", "$target_dir/$afile" ) {
+              msg( "mail,log", "rename: $!\n" );
+            } else {
+              msg( "mail,log", "$afile moved to $target_delay-day\n" );
+            }
+          } ## end for my $afile (@thesefiles)
+        } ## end else [ if ( $adelay > $conf::max_delayed)
+      } ## end else [ if ( @word != 3 )
+    } elsif ( $word[0] eq "cancel" ) {
+      if ( @word != 2 ) {
+        msg( "mail,log", "Wrong number of arguments\n" );
+      } elsif ( $conf::upload_method ne "copy" ) {
+        msg( "mail,log", "cancel not available\n" );
+      } elsif (
+          $word[1] !~ m,^[a-zA-Z0-9.+_:@=%-][~a-zA-Z0-9.+_:@=%-]*\.changes$, )
+      {
+        msg( "mail,log",
+          "argument to cancel must be one .changes filename without path\n" );
+      } ## end elsif ( $word[1] !~ ...
+      my (@files) = ();
+      for ( my ($adelay) = 0 ; $adelay <= $conf::max_delayed ; $adelay++ ) {
+        my ($dir) = sprintf( "$conf::targetdir_delayed", $adelay );
+        if ( -f "$dir/$word[1]" ) {
+          @removed = ();
+          push( @files, "$word[1]" );
+          push( @files,
+                get_filelist_from_known_good_changes("$dir/$word[1]") );
+          foreach $file (@files) {
+            if ( !-f "$dir/$file" ) {
+              msg( "mail,log", "$dir/$file: no such file\n" );
+            } elsif ( "$dir/$file" =~ /$conf::keep_files/ ) {
+              msg( "mail,log",
+                   "$dir/$file is protected, cannot " . "remove\n" );
+            } elsif ( !unlink("$dir/$file") ) {
+              msg( "mail,log", "$dir/$file: rm: $!\n" );
+            } else {
+              push( @removed, $file );
+            }
+          } ## end foreach $file (@files)
+          msg( "mail,log", "Files removed from $adelay-day: @removed\n" )
+            if @removed;
+        } ## end if ( -f "$dir/$word[1]")
+      } ## end for ( my ($adelay) = 0 ...
+      if ( !@files ) {
+        msg( "mail,log", "No upload found: $word[1]\n" );
+      }
+    } else {
+      msg( "mail,log", "unknown command $word[0]\n" );
+    }
+  } ## end foreach $cmd (@cmds)
+  rm($commands);
+  msg( "log",
+       "-- End of $main::current_incoming_short/$commands processing\n" );
+} ## end sub process_commands($)
+
+sub age_delayed_queues() {
+  for ( my ($adelay) = 0 ; $adelay <= $conf::max_delayed ; $adelay++ ) {
+    my ($dir) = sprintf( "$conf::targetdir_delayed", $adelay );
+    my ($target_dir);
+    if ( $adelay == 0 ) {
+      $target_dir = $conf::targetdir;
+    } else {
+      $target_dir = sprintf( "$conf::targetdir_delayed", $adelay - 1 );
+    }
+    for my $achanges (<$dir/*.changes>) {
+      my $mtime = ( stat($achanges) )[9];
+      if ( $mtime + 24 * 60 * 60 <= time || $adelay == 0 ) {
+        utime undef, undef, ($achanges);
+        my @thesefiles = ( $achanges =~ m,.*/([^/]*), );
+        push( @thesefiles, get_filelist_from_known_good_changes($achanges) );
+        for my $afile (@thesefiles) {
+          if ( !rename "$dir/$afile", "$target_dir/$afile" ) {
+            msg( "log", "rename: $!\n" );
+          } else {
+            msg( "log", "$afile moved to $target_dir\n" );
+          }
+        } ## end for my $afile (@thesefiles)
+      } ## end if ( $mtime + 24 * 60 ...
+    } ## end for my $achanges (<$dir/*.changes>)
+  } ## end for ( my ($adelay) = 0 ...
+} ## end sub age_delayed_queues()
+
+#
+# check if a file is already on target
+#
+sub is_on_target($\@) {
+  my $file     = shift;
+  my $filelist = shift;
+  my $msg;
+  my $stat;
+
+  if ( $conf::upload_method eq "ssh" ) {
+    ( $msg, $stat ) = ssh_cmd("ls -l $file");
+  } elsif ( $conf::upload_method eq "ftp" ) {
+    my $err;
+    ( $msg, $err ) = ftp_cmd( "dir", $file );
+    if ($err) {
+      $stat = 1;
+      $msg  = $err;
+    } elsif ( !$msg ) {
+      $stat = 1;
+      $msg  = "ls: no such file\n";
+    } else {
+      $stat = 0;
+      $msg = join( "\n", @$msg );
+    }
+  } else {
+    my @allfiles = ($file);
+    push( @allfiles, @$filelist );
+    $stat = 1;
+    $msg  = "no such file";
+    for my $afile (@allfiles) {
+      if ( -f "$conf::targetdir/$afile" ) {
+        $stat = 0;
+        $msg  = "$afile";
+      }
+    } ## end for my $afile (@allfiles)
+    for ( my ($adelay) = 0 ;
+          $adelay <= $conf::max_delayed && $stat ;
+          $adelay++ )
+    {
+      for my $afile (@allfiles) {
+        if (
+           -f ( sprintf( "$conf::targetdir_delayed", $adelay ) . "/$afile" ) )
+        {
+          $stat = 0;
+          $msg = sprintf( "%d-day", $adelay ) . "/$afile";
+        } ## end if ( -f ( sprintf( "$conf::targetdir_delayed"...
+      } ## end for my $afile (@allfiles)
+    } ## end for ( my ($adelay) = 0 ...
+  } ## end else [ if ( $conf::upload_method...
+  chomp($msg);
+  debug("exit status: $stat, output was: $msg");
+
+  return "" if $stat && $msg =~ /no such file/i;    # file not present
+  msg( "log", "strange ls -l output on target:\n", $msg ), return ""
+    if $stat || $@;    # some other error, but still try to upload
+
+  # ls -l returned 0 -> file already there
+  $msg =~ s/\s\s+/ /g;    # make multiple spaces into one, to save space
+  return $msg;
+} ## end sub is_on_target($\@)
+
+#
+# copy a list of files to target
+#
+sub copy_to_target(@) {
+  my @files = @_;
+  my ( @md5sum, @expected_files, $sum, $name, $msgs, $stat );
+
+  $main::dstat = "u";
+  write_status_file() if $conf::statusdelay;
+
+  # copy the files
+  if ( $conf::upload_method eq "ssh" ) {
+    ( $msgs, $stat ) = scp_cmd(@files);
+    goto err if $stat;
+  } elsif ( $conf::upload_method eq "ftp" ) {
+    my ( $rv, $file );
+    if ( !$main::FTP_chan->cwd($main::current_targetdir) ) {
+      msg( "log,mail",
+           "Can't cd to $main::current_targetdir on $conf::target\n" );
+      goto err;
+    }
+    foreach $file (@files) {
+      ( $rv, $msgs ) = ftp_cmd( "put", $file );
+      goto err if !$rv;
+    }
+  } else {
+    ( $msgs, $stat ) =
+      local_cmd( "$conf::cp @files $main::current_targetdir", 'NOCD' );
+    goto err if $stat;
+  }
+
+  # check md5sums or sizes on target against our own
+  my $have_md5sums = 1;
+  if ($conf::check_md5sum) {
+    if ( $conf::upload_method eq "ssh" ) {
+      ( $msgs, $stat ) = ssh_cmd("md5sum @files");
+      goto err if $stat;
+      @md5sum = split( "\n", $msgs );
+    } elsif ( $conf::upload_method eq "ftp" ) {
+      my ( $rv, $err, $file );
+      foreach $file (@files) {
+        ( $rv, $err ) = ftp_cmd( "quot", "site", "md5sum", $file );
+        if ($err) {
+          next if ftp_code() == 550;    # file not found
+          if ( ftp_code() == 500 ) {    # unimplemented
+            $have_md5sums = 0;
+            goto get_sizes_instead;
+          }
+          $msgs = $err;
+          goto err;
+        } ## end if ($err)
+        chomp( my $t = ftp_response() );
+        push( @md5sum, $t );
+      } ## end foreach $file (@files)
+      if ( !$have_md5sums ) {
+      get_sizes_instead:
+        foreach $file (@files) {
+          ( $rv, $err ) = ftp_cmd( "size", $file );
+          if ($err) {
+            next if ftp_code() == 550;    # file not found
+            $msgs = $err;
+            goto err;
+          }
+          push( @md5sum, "$rv $file" );
+        } ## end foreach $file (@files)
+      } ## end if ( !$have_md5sums )
+    } else {
+      ( $msgs, $stat ) = local_cmd("$conf::md5sum @files");
+      goto err if $stat;
+      @md5sum = split( "\n", $msgs );
+    }
+
+    @expected_files = @files;
+    foreach (@md5sum) {
+      chomp;
+      ( $sum, $name ) = split;
+      next if !grep { $_ eq $name } @files;    # a file we didn't upload??
+      next if $sum eq "md5sum:";               # looks like an error message
+      if (    ( $have_md5sums && $sum ne md5sum($name) )
+           || ( !$have_md5sums && $sum != ( -s $name ) ) )
+      {
+        msg(
+             "log,mail",
+             "Upload of $name to $conf::target failed ",
+             "(" . ( $have_md5sums ? "md5sum" : "size" ) . " mismatch)\n"
+           );
+        goto err;
+      } ## end if ( ( $have_md5sums &&...
+
+      # seen that file, remove it from expect list
+      @expected_files = map { $_ eq $name ? () : $_ } @expected_files;
+    } ## end foreach (@md5sum)
+    if (@expected_files) {
+      msg( "log,mail", "Failed to upload the files\n" );
+      msg( "log,mail", "  ", join( ", ", @expected_files ), "\n" );
+      msg( "log,mail", "(Not present on target after upload)\n" );
+      goto err;
+    } ## end if (@expected_files)
+  } ## end if ($conf::check_md5sum)
+
+  if ($conf::chmod_on_target) {
+
+    # change file's mode explicitly to 644 on target
+    if ( $conf::upload_method eq "ssh" ) {
+      ( $msgs, $stat ) = ssh_cmd("chmod 644 @files");
+      goto err if $stat;
+    } elsif ( $conf::upload_method eq "ftp" ) {
+      my ( $rv, $file );
+      foreach $file (@files) {
+        ( $rv, $msgs ) = ftp_cmd( "quot", "site", "chmod", "644", $file );
+        msg( "log", "Can't chmod $file on target:\n$msgs" )
+          if $msgs;
+        goto err if !$rv;
+      } ## end foreach $file (@files)
+    } else {
+      ( $msgs, $stat ) = local_cmd("$conf::chmod 644 @files");
+      goto err if $stat;
+    }
+  } ## end if ($conf::chmod_on_target)
+
+  $main::dstat = "c";
+  write_status_file() if $conf::statusdelay;
+  return 1;
+
+err:
+  msg( "log,mail",
+       "Upload to $conf::target failed",
+       $? ? ", last exit status " . sprintf( "%s", $? >> 8 ) : "", "\n" );
+  msg( "log,mail", "Error messages:\n", $msgs )
+    if $msgs;
+
+  # If "permission denied" was among the errors, test if the incoming is
+  # writable at all.
+  if ( $msgs =~ /(permission denied|read-?only file)/i ) {
+    if ( !check_incoming_writable() ) {
+      msg( "log,mail", "(The incoming directory seems to be ",
+           "unwritable.)\n" );
+    }
+  } ## end if ( $msgs =~ /(permission denied|read-?only file)/i)
+
+  # remove bad files or an incomplete upload on target
+  if ( $conf::upload_method eq "ssh" ) {
+    ssh_cmd("rm -f @files");
+  } elsif ( $conf::upload_method eq "ftp" ) {
+    my $file;
+    foreach $file (@files) {
+      my ( $rv, $err );
+      ( $rv, $err ) = ftp_cmd( "delete", $file );
+      msg( "log", "Can't delete $file on target:\n$err" )
+        if $err;
+    } ## end foreach $file (@files)
+  } else {
+    my @tfiles = map { "$main::current_targetdir/$_" } @files;
+    debug("executing unlink(@tfiles)");
+    rm(@tfiles);
+  }
+  $main::dstat = "c";
+  write_status_file() if $conf::statusdelay;
+  return 0;
+} ## end sub copy_to_target(@)
+
+#
+# check if a file is correctly signed with PGP
+#
+sub pgp_check($) {
+  my $file   = shift;
+  my $output = "";
+  my $signator;
+  my $found = 0;
+  my $stat;
+  local (*PIPE);
+
+  $stat = 1;
+  if ( -x $conf::gpg ) {
+    debug(   "executing $conf::gpg --no-options --batch "
+           . "--no-default-keyring --always-trust "
+           . "--keyring "
+           . join( " --keyring ", @conf::keyrings )
+           . " --verify '$file'" );
+    if (
+         !open( PIPE,
+                    "$conf::gpg --no-options --batch "
+                  . "--no-default-keyring --always-trust "
+                  . "--keyring "
+                  . join( " --keyring ", @conf::keyrings )
+                  . " --verify '$file'"
+                  . " 2>&1 |"
+              )
+       )
+    {
+      msg( "log", "Can't open pipe to $conf::gpg: $!\n" );
+      return "LOCAL ERROR";
+    } ## end if ( !open( PIPE, "$conf::gpg --no-options --batch "...
+    $output .= $_ while (<PIPE>);
+    close(PIPE);
+    $stat = $?;
+  } ## end if ( -x $conf::gpg )
+
+  if ($stat) {
+    msg( "log,mail", "GnuPG signature check failed on $file\n" );
+    msg( "mail",     $output );
+    msg( "log,mail", "(Exit status ", $stat >> 8, ")\n" );
+    return "";
+  } ## end if ($stat)
+
+  $output =~ /^(gpg: )?good signature from (user )?"(.*)"\.?$/im;
+  ( $signator = $3 ) ||= "unknown signator";
+  if ($conf::debug) {
+    debug("GnuPG signature ok (by $signator)");
+  }
+  return $signator;
+} ## end sub pgp_check($)
+
+# ---------------------------------------------------------------------------
+#                                                        the status daemon
+# ---------------------------------------------------------------------------
+
+#
+# fork a subprocess that watches the 'status' FIFO
+#
+# that process blocks until someone opens the FIFO, then sends a
+# signal (SIGUSR1) to the main process, expects
+#
+sub fork_statusd() {
+  my $statusd_pid;
+  my $main_pid = $$;
+  my $errs;
+  local (*STATFIFO);
+
+  $statusd_pid = open( STATUSD, "|-" );
+  die "cannot fork: $!\n" if !defined($statusd_pid);
+
+  # parent just returns
+  if ($statusd_pid) {
+    msg( "log", "forked status daemon (pid $statusd_pid)\n" );
+    return $statusd_pid;
+  }
+
+  # child: the status FIFO daemon
+
+  # ignore SIGPIPE here, in case some closes the FIFO without completely
+  # reading it
+  $SIG{"PIPE"} = "IGNORE";
+
+  # also ignore SIGCLD, we don't want to inherit the restart-statusd handler
+  # from our parent
+  $SIG{"CHLD"} = "DEFAULT";
+
+  rm($conf::statusfile);
+  $errs = `$conf::mkfifo $conf::statusfile`;
+  die "$main::progname: cannot create named pipe $conf::statusfile: $errs"
+    if $?;
+  chmod( 0644, $conf::statusfile )
+    or die "Cannot set modes of $conf::statusfile: $!\n";
+
+  # close log file, so that log rotating works
+  close(LOG);
+  close(STDOUT);
+  close(STDERR);
+
+  while (1) {
+    my ( $status, $mup, $incw, $ds, $next_run, $last_ping, $currch, $l );
+
+    # open the FIFO for writing; this blocks until someone (probably ftpd)
+    # opens it for reading
+    open( STATFIFO, ">$conf::statusfile" )
+      or die "Cannot open $conf::statusfile\n";
+    select(STATFIFO);
+
+    # tell main daemon to send us status infos
+    kill( $main::signo{"USR1"}, $main_pid );
+
+    # get the infos from stdin; must loop until enough bytes received!
+    my $expect_len = 3 + 2 * STATNUM_LEN + STATSTR_LEN;
+    for ( $status = "" ; ( $l = length($status) ) < $expect_len ; ) {
+      sysread( STDIN, $status, $expect_len - $l, $l );
+    }
+
+    # disassemble the status byte stream
+    my $pos = 0;
+    foreach (
+              [ mup       => 1 ],
+              [ incw      => 1 ],
+              [ ds        => 1 ],
+              [ next_run  => STATNUM_LEN ],
+              [ last_ping => STATNUM_LEN ],
+              [ currch    => STATSTR_LEN ]
+            )
+    {
+      eval "\$$_->[0] = substr( \$status, $pos, $_->[1] );";
+      $pos += $_->[1];
+    } ## end foreach ( [ mup => 1 ], [ incw...
+    $currch =~ s/\n+//g;
+
+    print_status( $mup, $incw, $ds, $next_run, $last_ping, $currch );
+    close(STATFIFO);
+
+    # This sleep is necessary so that we can't reopen the FIFO
+    # immediately, in case the reader hasn't closed it yet if we get to
+    # the open again. Is there a better solution for this??
+    sleep 1;
+  } ## end while (1)
+} ## end sub fork_statusd()
+
+#
+# update the status file, in case we use a plain file and not a FIFO
+#
+sub write_status_file() {
+
+  return if !$conf::statusfile;
+
+  open( STATFILE, ">$conf::statusfile" )
+    or ( msg( "log", "Could not open $conf::statusfile: $!\n" ), return );
+  my $oldsel = select(STATFILE);
+
+  print_status(
+                $main::target_up,      $main::incoming_writable,
+                $main::dstat,          $main::next_run,
+                $main::last_ping_time, $main::current_changes
+              );
+
+  select($oldsel);
+  close(STATFILE);
+} ## end sub write_status_file()
+
+sub print_status($$$$$$) {
+  my $mup       = shift;
+  my $incw      = shift;
+  my $ds        = shift;
+  my $next_run  = shift;
+  my $last_ping = shift;
+  my $currch    = shift;
+  my $approx;
+  my $version;
+
+  ( $version = 'Release: 0.9 $Revision: 1.51 $' ) =~ s/\$ ?//g;
+  print "debianqueued $version\n";
+
+  $approx = $conf::statusdelay ? "approx. " : "";
+
+  if ( $mup eq "0" ) {
+    print "$conf::target is down, queue pausing\n";
+    return;
+  } elsif ( $conf::upload_method ne "copy" ) {
+    print "$conf::target seems to be up, last ping $approx",
+      print_time( time - $last_ping ), " ago\n";
+  }
+
+  if ( $incw eq "0" ) {
+    print "The incoming directory is not writable, queue pausing\n";
+    return;
+  }
+
+  if ( $ds eq "i" ) {
+    print "Next queue check in $approx", print_time( $next_run - time ), "\n";
+    return;
+  } elsif ( $ds eq "c" ) {
+    print "Checking queue directory\n";
+  } elsif ( $ds eq "u" ) {
+    print "Uploading to $conf::target\n";
+  } else {
+    print "Bad status data from daemon: \"$mup$incw$ds\"\n";
+    return;
+  }
+
+  print "Current job is $currch\n" if $currch;
+} ## end sub print_status($$$$$$)
+
+#
+# format a number for sending to statusd (fixed length STATNUM_LEN)
+#
+sub format_status_num(\$$) {
+  my $varref = shift;
+  my $num    = shift;
+
+  $$varref = sprintf "%" . STATNUM_LEN . "d", $num;
+} ## end sub format_status_num(\$$)
+
+#
+# format a string for sending to statusd (fixed length STATSTR_LEN)
+#
+sub format_status_str(\$$) {
+  my $varref = shift;
+  my $str    = shift;
+
+  $$varref = substr( $str, 0, STATSTR_LEN );
+  $$varref .= "\n" x ( STATSTR_LEN - length($$varref) );
+} ## end sub format_status_str(\$$)
+
+#
+# send a status string to the status daemon
+#
+# Avoid all operations that could call malloc() here! Most libc
+# implementations aren't reentrant, so we may not call it from a
+# signal handler. So use only already-defined variables.
+#
+sub send_status() {
+  local $! = 0;    # preserve errno
+
+  # re-setup handler, in case we have broken SysV signals
+  $SIG{"USR1"} = \&send_status;
+
+  syswrite( STATUSD, $main::target_up,         1 );
+  syswrite( STATUSD, $main::incoming_writable, 1 );
+  syswrite( STATUSD, $main::dstat,             1 );
+  syswrite( STATUSD, $main::next_run,          STATNUM_LEN );
+  syswrite( STATUSD, $main::last_ping_time,    STATNUM_LEN );
+  syswrite( STATUSD, $main::current_changes,   STATSTR_LEN );
+} ## end sub send_status()
+
+# ---------------------------------------------------------------------------
+#                                                          FTP functions
+# ---------------------------------------------------------------------------
+
+#
+# open FTP connection to target host if not already open
+#
+sub ftp_open() {
+
+  if ($main::FTP_chan) {
+
+    # is already open, but might have timed out; test with a cwd
+    return $main::FTP_chan
+      if $main::FTP_chan->cwd($main::current_targetdir);
+
+    # cwd didn't work, channel is closed, try to reopen it
+    $main::FTP_chan = undef;
+  } ## end if ($main::FTP_chan)
+
+  if (
+       !(
+          $main::FTP_chan =
+          Net::FTP->new(
+                         $conf::target,
+                         Debug   => $conf::ftpdebug,
+                         Timeout => $conf::ftptimeout,
+                         Passive => 1,
+                       )
+        )
+     )
+  {
+    msg( "log,mail", "Cannot open FTP server $conf::target\n" );
+    goto err;
+  } ## end if ( !( $main::FTP_chan...
+  if ( !$main::FTP_chan->login() ) {
+    msg( "log,mail", "Anonymous login on FTP server $conf::target failed\n" );
+    goto err;
+  }
+  if ( !$main::FTP_chan->binary() ) {
+    msg( "log,mail", "Can't set binary FTP mode on $conf::target\n" );
+    goto err;
+  }
+  if ( !$main::FTP_chan->cwd($main::current_targetdir) ) {
+    msg( "log,mail",
+         "Can't cd to $main::current_targetdir on $conf::target\n" );
+    goto err;
+  }
+  debug("opened FTP channel to $conf::target");
+  return 1;
+
+err:
+  $main::FTP_chan = undef;
+  return 0;
+} ## end sub ftp_open()
+
+sub ftp_cmd($@) {
+  my $cmd = shift;
+  my ( $rv, $err );
+  my $direct_resp_cmd = ( $cmd eq "quot" );
+
+  debug( "executing FTP::$cmd(" . join( ", ", @_ ) . ")" );
+  $SIG{"ALRM"} = sub { die "timeout in FTP::$cmd\n" };
+  alarm($conf::remote_timeout);
+  eval { $rv = $main::FTP_chan->$cmd(@_); };
+  alarm(0);
+  $err = "";
+  $rv = ( ftp_code() =~ /^2/ ) ? 1 : 0 if $direct_resp_cmd;
+  if ($@) {
+    $err = $@;
+    undef $rv;
+  } elsif ( !$rv ) {
+    $err = ftp_response();
+  }
+  return ( $rv, $err );
+} ## end sub ftp_cmd($@)
+
+sub ftp_close() {
+  if ($main::FTP_chan) {
+    $main::FTP_chan->quit();
+    $main::FTP_chan = undef;
+  }
+  return 1;
+} ## end sub ftp_close()
+
+sub ftp_response() {
+  return join( '', @{ ${*$main::FTP_chan}{'net_cmd_resp'} } );
+}
+
+sub ftp_code() {
+  return ${*$main::FTP_chan}{'net_cmd_code'};
+}
+
+sub ftp_error() {
+  my $code = ftp_code();
+  return ( $code =~ /^[45]/ ) ? 1 : 0;
+}
+
+# ---------------------------------------------------------------------------
+#                                                        utility functions
+# ---------------------------------------------------------------------------
+
+sub ssh_cmd($) {
+  my $cmd = shift;
+  my ( $msg, $stat );
+
+  my $ecmd = "$conf::ssh $conf::ssh_options $conf::target "
+    . "-l $conf::targetlogin \'cd $main::current_targetdir; $cmd\'";
+  debug("executing $ecmd");
+  $SIG{"ALRM"} = sub { die "timeout in ssh command\n" };
+  alarm($conf::remote_timeout);
+  eval { $msg = `$ecmd 2>&1`; };
+  alarm(0);
+  if ($@) {
+    $msg  = $@;
+    $stat = 1;
+  } else {
+    $stat = $?;
+  }
+  return ( $msg, $stat );
+} ## end sub ssh_cmd($)
+
+sub scp_cmd(@) {
+  my ( $msg, $stat );
+
+  my $ecmd = "$conf::scp $conf::ssh_options @_ "
+    . "$conf::targetlogin\@$conf::target:$main::current_targetdir";
+  debug("executing $ecmd");
+  $SIG{"ALRM"} = sub { die "timeout in scp\n" };
+  alarm($conf::remote_timeout);
+  eval { $msg = `$ecmd 2>&1`; };
+  alarm(0);
+  if ($@) {
+    $msg  = $@;
+    $stat = 1;
+  } else {
+    $stat = $?;
+  }
+  return ( $msg, $stat );
+} ## end sub scp_cmd(@)
+
+sub local_cmd($;$) {
+  my $cmd  = shift;
+  my $nocd = shift;
+  my ( $msg, $stat );
+
+  my $ecmd = ( $nocd ? "" : "cd $main::current_targetdir; " ) . $cmd;
+  debug("executing $ecmd");
+  $msg  = `($ecmd) 2>&1`;
+  $stat = $?;
+  return ( $msg, $stat );
+
+} ## end sub local_cmd($;$)
+
+#
+# check if target is alive (code stolen from Net::Ping.pm)
+#
+sub check_alive(;$) {
+  my $timeout = shift;
+  my ( $saddr, $ret, $target_ip );
+  local (*PINGSOCK);
+
+  if ( $conf::upload_method eq "copy" ) {
+    format_status_num( $main::last_ping_time, time );
+    $main::target_up = 1;
+    return;
+  }
+
+  $timeout ||= 30;
+
+  if ( !( $target_ip = ( gethostbyname($conf::target) )[4] ) ) {
+    msg( "log", "Cannot get IP address of $conf::target\n" );
+    $ret = 0;
+    goto out;
+  }
+  $saddr = pack( 'S n a4 x8', AF_INET, $main::echo_port, $target_ip );
+  $SIG{'ALRM'} = sub { die };
+  alarm($timeout);
+
+  $ret = $main::tcp_proto;    # avoid warnings about unused variable
+  $ret = 0;
+  eval <<'EOM' ;
+    return unless socket( PINGSOCK, PF_INET, SOCK_STREAM, $main::tcp_proto );
+    return unless connect( PINGSOCK, $saddr );
+    $ret = 1;
+EOM
+  alarm(0);
+  close(PINGSOCK);
+  msg( "log", "pinging $conf::target: " . ( $ret ? "ok" : "down" ) . "\n" );
+out:
+  $main::target_up = $ret ? "1" : "0";
+  format_status_num( $main::last_ping_time, time );
+  write_status_file() if $conf::statusdelay;
+} ## end sub check_alive(;$)
+
+#
+# check if incoming dir on target is writable
+#
+sub check_incoming_writable() {
+  my $testfile = ".debianqueued-testfile";
+  my ( $msg, $stat );
+
+  if ( $conf::upload_method eq "ssh" ) {
+    ( $msg, $stat ) =
+      ssh_cmd( "rm -f $testfile; touch $testfile; " . "rm -f $testfile" );
+  } elsif ( $conf::upload_method eq "ftp" ) {
+    my $file = "junk-for-writable-test-" . format_time();
+    $file =~ s/[ :.]/-/g;
+    local (*F);
+    open( F, ">$file" );
+    close(F);
+    my $rv;
+    ( $rv, $msg ) = ftp_cmd( "put", $file );
+    $stat = 0;
+    $msg = "" if !defined $msg;
+    unlink $file;
+    ftp_cmd( "delete", $file );
+  } elsif ( $conf::upload_method eq "copy" ) {
+    ( $msg, $stat ) =
+      local_cmd( "rm -f $testfile; touch $testfile; " . "rm -f $testfile" );
+  }
+  chomp($msg);
+  debug("exit status: $stat, output was: $msg");
+
+  if ( !$stat ) {
+
+    # change incoming_writable only if ssh didn't return an error
+    $main::incoming_writable =
+      ( $msg =~ /(permission denied|read-?only file|cannot create)/i )
+      ? "0"
+      : "1";
+  } else {
+    debug("local error, keeping old status");
+  }
+  debug("incoming_writable = $main::incoming_writable");
+  write_status_file() if $conf::statusdelay;
+  return $main::incoming_writable;
+} ## end sub check_incoming_writable()
+
+#
+# remove a list of files, log failing ones
+#
+sub rm(@) {
+  my $done = 0;
+
+  foreach (@_) {
+    ( unlink $_ and ++$done )
+      or $! == ENOENT
+      or msg( "log", "Could not delete $_: $!\n" );
+  }
+  return $done;
+} ## end sub rm(@)
+
+#
+# get md5 checksum of a file
+#
+sub md5sum($) {
+  my $file = shift;
+  my $line;
+
+  chomp( $line = `$conf::md5sum $file` );
+  debug( "md5sum($file): ",
+           $? ? "exit status $?"
+         : $line =~ /^(\S+)/ ? $1
+         :                     "match failed" );
+  return $? ? "" : $line =~ /^(\S+)/ ? $1 : "";
+} ## end sub md5sum($)
+
+#
+# check if a file probably belongs to a Debian upload
+#
+sub is_debian_file($) {
+  my $file = shift;
+  return $file =~ /\.(deb|dsc|(diff|tar)\.gz)$/
+    && $file !~ /\.orig\.tar\.gz/;
+}
+
+#
+# try to extract maintainer email address from some a non-.changes file
+# return "" if not possible
+#
+sub get_maintainer($) {
+  my $file       = shift;
+  my $maintainer = "";
+  local (*F);
+
+  if ( $file =~ /\.diff\.gz$/ ) {
+
+    # parse a diff
+    open( F, "$conf::gzip -dc '$file' 2>/dev/null |" ) or return "";
+    while (<F>) {
+
+      # look for header line of a file */debian/control
+      last if m,^\+\+\+\s+[^/]+/debian/control(\s+|$),;
+    }
+    while (<F>) {
+      last if /^---/;   # end of control file patch, no Maintainer: found
+                        # inside control file patch look for Maintainer: field
+      $maintainer = $1, last if /^\+Maintainer:\s*(.*)$/i;
+    }
+    while (<F>) { }     # read to end of file to avoid broken pipe
+    close(F) or return "";
+  } elsif ( $file =~ /\.(deb|dsc|tar\.gz)$/ ) {
+    if ( $file =~ /\.deb$/ && $conf::ar ) {
+
+      # extract control.tar.gz from .deb with ar, then let tar extract
+      # the control file itself
+      open( F,
+                "($conf::ar p '$file' control.tar.gz | "
+              . "$conf::tar -xOf - "
+              . "--use-compress-program $conf::gzip "
+              . "control) 2>/dev/null |"
+          ) or return "";
+    } elsif ( $file =~ /\.dsc$/ ) {
+
+      # just do a plain grep
+      debug("get_maint: .dsc, no cmd");
+      open( F, "<$file" ) or return "";
+    } elsif ( $file =~ /\.tar\.gz$/ ) {
+
+      # let tar extract a file */debian/control
+      open( F,
+                "$conf::tar -xOf '$file' "
+              . "--use-compress-program $conf::gzip "
+              . "\\*/debian/control 2>&1 |"
+          ) or return "";
+    } else {
+      return "";
+    }
+    while (<F>) {
+      $maintainer = $1, last if /^Maintainer:\s*(.*)$/i;
+    }
+    close(F) or return "";
+  } ## end elsif ( $file =~ /\.(deb|dsc|tar\.gz)$/)
+
+  return $maintainer;
+} ## end sub get_maintainer($)
+
+#
+# return a pattern that matches all files that probably belong to one job
+#
+sub debian_file_stem($) {
+  my $file = shift;
+  my ( $pkg, $version );
+
+  # strip file suffix
+  $file =~ s,\.(deb|dsc|changes|(orig\.)?tar\.gz|diff\.gz)$,,;
+
+  # if not is *_* (name_version), can't derive a stem and return just
+  # the file's name
+  return $file if !( $file =~ /^([^_]+)_([^_]+)/ );
+  ( $pkg, $version ) = ( $1, $2 );
+
+  # strip Debian revision from version
+  $version =~ s/^(.*)-[\d.+-]+$/$1/;
+
+  return "${pkg}_${version}*";
+} ## end sub debian_file_stem($)
+
+#
+# output a messages to several destinations
+#
+# first arg is a comma-separated list of destinations; valid are "log"
+# and "mail"; rest is stuff to be printed, just as with print
+#
+sub msg($@) {
+  my @dest = split( ',', shift );
+
+  if ( grep /log/, @dest ) {
+    my $now = format_time();
+    print LOG "$now ", @_;
+  }
+
+  if ( grep /mail/, @dest ) {
+    $main::mail_text .= join( '', @_ );
+  }
+} ## end sub msg($@)
+
+#
+# print a debug messages, if $debug is true
+#
+sub debug(@) {
+  return if !$conf::debug;
+  my $now = format_time();
+  print LOG "$now DEBUG ", @_, "\n";
+}
+
+#
+# intialize the "mail" destination of msg() (this clears text,
+# address, subject, ...)
+#
+sub init_mail(;$) {
+  my $file = shift;
+
+  $main::mail_addr    = "";
+  $main::mail_text    = "";
+  %main::packages     = ();
+  $main::mail_subject = $file ? "Processing of $file" : "";
+} ## end sub init_mail(;$)
+
+#
+# finalize mail to be sent from msg(): check if something present, and
+# then send out
+#
+sub finish_mail() {
+
+  debug("No mail for $main::mail_addr")
+    if $main::mail_addr && !$main::mail_text;
+  return unless $main::mail_addr && $main::mail_text;
+
+  if ( !send_mail( $main::mail_addr, $main::mail_subject, $main::mail_text ) )
+  {
+
+    # store this mail in memory so it isn't lost if executing sendmail
+    # failed.
+    push(
+          @main::stored_mails,
+          {
+            addr    => $main::mail_addr,
+            subject => $main::mail_subject,
+            text    => $main::mail_text
+          }
+        );
+  } ## end if ( !send_mail( $main::mail_addr...
+  init_mail();
+
+  # try to send out stored mails
+  my $mailref;
+  while ( $mailref = shift(@main::stored_mails) ) {
+    if (
+         !send_mail( $mailref->{'addr'}, $mailref->{'subject'},
+                     $mailref->{'text'} )
+       )
+    {
+      unshift( @main::stored_mails, $mailref );
+      last;
+    } ## end if ( !send_mail( $mailref...
+  } ## end while ( $mailref = shift(...
+} ## end sub finish_mail()
+
+#
+# send one mail
+#
+sub send_mail($$$) {
+  my $addr    = shift;
+  my $subject = shift;
+  my $text    = shift;
+
+  my $package =
+    keys %main::packages ? join( ' ', keys %main::packages ) : "";
+
+  use Email::Send;
+
+  unless ( defined($Email::Send::Sendmail::SENDMAIL) ) {
+    $Email::Send::Sendmail::SENDMAIL = $conf::mail;
+  }
+
+  my $date = sprintf "%s",
+    strftime( "%a, %d %b %Y %T %z", ( localtime(time) ) );
+  my $message = <<__MESSAGE__;
+To: $addr
+From: Archive Administrator <dak\@ftp-master.debian.org>
+Subject: $subject
+Date: $date
+X-Debian: DAK
+__MESSAGE__
+
+  if ( length $package ) {
+    $message .= "X-Debian-Package: $package\n";
+  }
+
+  $message .= "\n$text";
+  $message .= "\nGreetings,\n\n\tYour Debian queue daemon\n";
+
+  my $mail = Email::Send->new;
+  for (qw[Sendmail SMTP]) {
+    $mail->mailer($_) and last if $mail->mailer_available($_);
+  }
+
+  my $ret = $mail->send($message);
+  if ( $ret && $ret !~ /Message sent|success/ ) {
+    return 0;
+  }
+
+  return 1;
+} ## end sub send_mail($$$)
+
+#
+# try to find a mail address for a name in the keyrings
+#
+sub try_to_get_mail_addr($$) {
+  my $name    = shift;
+  my $listref = shift;
+
+  @$listref = ();
+  open( F,
+            "$conf::gpg --no-options --batch --no-default-keyring "
+          . "--always-trust --keyring "
+          . join( " --keyring ", @conf::keyrings )
+          . " --list-keys |"
+      ) or return "";
+  while (<F>) {
+    if ( /^pub / && / $name / ) {
+      /<([^>]*)>/;
+      push( @$listref, $1 );
+    }
+  } ## end while (<F>)
+  close(F);
+
+  return ( @$listref >= 1 ) ? $listref->[0] : "";
+} ## end sub try_to_get_mail_addr($$)
+
+#
+# return current time as string
+#
+sub format_time() {
+  my $t;
+
+  # omit weekday and year for brevity
+  ( $t = localtime ) =~ /^\w+\s(.*)\s\d+$/;
+  return $1;
+} ## end sub format_time()
+
+sub print_time($) {
+  my $secs = shift;
+  my $hours = int( $secs / ( 60 * 60 ) );
+
+  $secs -= $hours * 60 * 60;
+  return sprintf "%d:%02d:%02d", $hours, int( $secs / 60 ), $secs % 60;
+} ## end sub print_time($)
+
+#
+# block some signals during queue processing
+#
+# This is just to avoid data inconsistency or uploads being aborted in the
+# middle. Only "soft" signals are blocked, i.e. SIGINT and SIGTERM, try harder
+# ones if you really want to kill the daemon at once.
+#
+sub block_signals() {
+  POSIX::sigprocmask( SIG_BLOCK, $main::block_sigset );
+}
+
+sub unblock_signals() {
+  POSIX::sigprocmask( SIG_UNBLOCK, $main::block_sigset );
+}
+
+#
+# process SIGHUP: close log file and reopen it (for logfile cycling)
+#
+sub close_log($) {
+  close(LOG);
+  close(STDOUT);
+  close(STDERR);
+
+  open( LOG, ">>$conf::logfile" )
+    or die "Cannot open my logfile $conf::logfile: $!\n";
+  chmod( 0644, $conf::logfile )
+    or msg( "log", "Cannot set modes of $conf::logfile: $!\n" );
+  select( ( select(LOG), $| = 1 )[0] );
+
+  open( STDOUT, ">&LOG" )
+    or msg( "log",
+      "$main::progname: Can't redirect stdout to " . "$conf::logfile: $!\n" );
+  open( STDERR, ">&LOG" )
+    or msg( "log",
+      "$main::progname: Can't redirect stderr to " . "$conf::logfile: $!\n" );
+  msg( "log", "Restart after SIGHUP\n" );
+} ## end sub close_log($)
+
+#
+# process SIGCHLD: check if it was our statusd process
+#
+sub kid_died($) {
+  my $pid;
+
+  # reap statusd, so that it's no zombie when we try to kill(0) it
+  waitpid( $main::statusd_pid, WNOHANG );
+
+  # Uncomment the following line if your Perl uses unreliable System V signal
+  # (i.e. if handlers reset to default if the signal is delivered).
+  # (Unfortunately, the re-setup can't be done in any case, since on some
+  # systems this will cause the SIGCHLD to be delivered again if there are
+  # still unreaped children :-(( )
+
+  #     $SIG{"CHLD"} = \&kid_died; # resetup handler for SysV
+} ## end sub kid_died($)
+
+sub restart_statusd() {
+
+  # restart statusd if it died
+  if ( !kill( 0, $main::statusd_pid ) ) {
+    close(STATUSD);    # close out pipe end
+    $main::statusd_pid = fork_statusd();
+  }
+} ## end sub restart_statusd()
+
+#
+# process a fatal signal: cleanup and exit
+#
+sub fatal_signal($) {
+  my $signame = shift;
+  my $sig;
+
+  # avoid recursions of fatal_signal in case of BSD signals
+  foreach $sig (qw( ILL ABRT BUS FPE SEGV PIPE )) {
+    $SIG{$sig} = "DEFAULT";
+  }
+
+  if ( $$ == $main::maind_pid ) {
+
+    # only the main daemon should do this
+    kill( $main::signo{"TERM"}, $main::statusd_pid )
+      if defined $main::statusd_pid;
+    unlink( $conf::statusfile, $conf::pidfile );
+  } ## end if ( $$ == $main::maind_pid)
+  msg( "log", "Caught SIG$signame -- exiting (pid $$)\n" );
+  exit 1;
+} ## end sub fatal_signal($)
+
+# Local Variables:
+#  tab-width: 4
+#  fill-column: 78
+# End:
diff --git a/tools/debianqueued-0.9/dqueued-watcher b/tools/debianqueued-0.9/dqueued-watcher
new file mode 100755 (executable)
index 0000000..b44470a
--- /dev/null
@@ -0,0 +1,504 @@
+#!/usr/bin/perl -w
+#
+# dqueued-watcher -- for regularily watching the queue daemon
+#
+# This script is intended to check periodically (e.g. started by cron) that
+# everything is ok with debianqueued. If the daemon isn't running, it notifies
+# the maintainer. It also checks if a new Debian keyring is available (in a
+# Debian mirror aera, f.i.) and then updates the keyring used by debianqueued.
+#
+# Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+#
+# This program is free software.  You can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation: either version 2 or
+# (at your option) any later version.
+# This program comes with ABSOLUTELY NO WARRANTY!
+#
+# $Id: dqueued-watcher,v 1.28 1999/07/08 09:43:22 ftplinux Exp $
+#
+# $Log: dqueued-watcher,v $
+# Revision 1.28  1999/07/08 09:43:22  ftplinux
+# Bumped release number to 0.9
+#
+# Revision 1.27  1999/07/07 11:58:22  ftplinux
+# Also update gpg keyring if $conf::gpg_keyring is set.
+#
+# Revision 1.26  1998/07/06 14:24:36  ftplinux
+# Some changes to handle debian-keyring.tar.gz files which expand to a
+# directory including a date.
+#
+# Revision 1.25  1998/05/14 14:21:45  ftplinux
+# Bumped release number to 0.8
+#
+# Revision 1.24  1998/03/30 12:31:05  ftplinux
+# Don't count "already reported" or "ignored for now" errors as .changes errors.
+# Also list files for several error types.
+# Also print out names of processed jobs.
+#
+# Revision 1.23  1998/03/30 11:27:37  ftplinux
+# If called with args, make summaries for the log files given.
+# make_summary: New arg $to_stdout, for printing report directly.
+#
+# Revision 1.22  1998/03/23 14:05:15  ftplinux
+# Bumped release number to 0.7
+#
+# Revision 1.21  1997/12/16 13:19:29  ftplinux
+# Bumped release number to 0.6
+#
+# Revision 1.20  1997/11/20 15:18:48  ftplinux
+# Bumped release number to 0.5
+#
+# Revision 1.19  1997/10/31 12:26:31  ftplinux
+# Again added new counters in make_summary: suspicious_files,
+# transient_changes_errs.
+# Extended tests for bad_changes.
+# Quotes in pattern seem not to work, replaced by '.'.
+#
+# Revision 1.18  1997/10/30 14:17:32  ftplinux
+# In make_summary, implemented some new counters for command files.
+#
+# Revision 1.17  1997/10/17 09:39:09  ftplinux
+# Fixed wrong args to plural_s
+#
+# Revision 1.16  1997/09/25 11:20:42  ftplinux
+# Bumped release number to 0.4
+#
+# Revision 1.15  1997/09/17 12:16:33  ftplinux
+# Added writing summaries to a file
+#
+# Revision 1.14  1997/09/16 11:39:29  ftplinux
+# In make_summary, initialize all counters to avoid warnings about uninited
+# values.
+#
+# Revision 1.13  1997/09/16 10:53:36  ftplinux
+# Made logging more verbose in queued and dqueued-watcher
+#
+# Revision 1.12  1997/08/18 13:07:15  ftplinux
+# Implemented summary mails
+#
+# Revision 1.11  1997/08/18 12:11:44  ftplinux
+# Replaced timegm by timelocal in parse_date; times in log file are
+# local times...
+#
+# Revision 1.10  1997/08/18 11:27:20  ftplinux
+# Revised age calculation of log file for rotating
+#
+# Revision 1.9  1997/08/12 09:54:40  ftplinux
+# Bumped release number
+#
+# Revision 1.8  1997/08/11 12:49:10  ftplinux
+# Implemented logfile rotating
+#
+# Revision 1.7  1997/07/28 13:20:38  ftplinux
+# Added release numner to startup message
+#
+# Revision 1.6  1997/07/25 10:23:04  ftplinux
+# Made SIGCHLD handling more portable between perl versions
+#
+# Revision 1.5  1997/07/09 10:13:55  ftplinux
+# Alternative implementation of status file as plain file (not FIFO), because
+# standard wu-ftpd doesn't allow retrieval of non-regular files. New config
+# option $statusdelay for this.
+#
+# Revision 1.4  1997/07/08 08:39:56  ftplinux
+# Need to remove -z from tar options if --use-compress-program
+#
+# Revision 1.3  1997/07/08 08:34:15  ftplinux
+# If dqueued-watcher runs as cron job, $PATH might not contain gzip. Use extra
+# --use-compress-program option to tar, and new config var $gzip.
+#
+# Revision 1.2  1997/07/03 13:05:57  ftplinux
+# Added some verbosity if stdin is a terminal
+#
+# Revision 1.1.1.1  1997/07/03 12:54:59  ftplinux
+# Import initial sources
+#
+#
+
+require 5.002;
+use strict;
+use POSIX;
+require "timelocal.pl";
+
+sub LINEWIDTH { 79 }
+my $batchmode = !(-t STDIN);
+$main::curr_year = (localtime)[5];
+
+do {
+       my $version;
+       ($version = 'Release: 0.9 $Revision: 1.28 $ $Date: 1999/07/08 09:43:22 $ $Author: ftplinux $') =~ s/\$ ?//g;
+       print "dqueued-watcher $version\n" if !$batchmode;
+};
+
+package conf;
+($conf::queued_dir = (($0 !~ m,^/,) ? POSIX::getcwd()."/" : "") . $0)
+       =~ s,/[^/]+$,,;
+require "$conf::queued_dir/config";
+my # avoid spurious warnings about unused vars
+$junk = $conf::gzip;
+$junk = $conf::maintainer_mail;
+$junk = $conf::log_age;
+package main;
+
+# prototypes
+sub check_daemon();
+sub daemon_running();
+sub rotate_log();
+sub logf($);
+sub parse_date($);
+sub make_summary($$$);
+sub stimes($);
+sub plural_s($);
+sub format_list($@);
+sub mail($@);
+sub logger(@);
+sub format_time();
+
+# the main program:
+if (@ARGV) {
+       # with arguments, make summaries (to stdout) for the logfiles given
+       foreach (@ARGV) {
+               make_summary( 1, undef, $_ );
+       }
+}
+else {
+       # without args, just do normal maintainance actions
+       check_daemon();
+       rotate_log();
+}
+exit 0;
+
+
+#
+# check if the daemon is running, notify maintainer if not
+#
+sub check_daemon() {
+       my $daemon_down_text = "Daemon is not running\n";
+       my( $line, $reported );
+
+       if (daemon_running()) {
+               print "Daemon is running\n" if !$batchmode;
+               return;
+       }
+       print "Daemon is NOT running!\n" if !$batchmode;
+
+       $reported = 0;
+       if ($conf::statusfile && -f $conf::statusfile && ! -p _ &&
+               open( STATUSFILE, "<$conf::statusfile" )) {
+               $line = <STATUSFILE>;
+               close( STATUSFILE );
+               $reported = $line eq $daemon_down_text;
+       }
+       if (!$reported) {
+               mail( "debianqueued down",
+                         "The Debian queue daemon isn't running!\n",
+                         "Please start it up again.\n" );
+               logger( "Found that daemon is not running\n" );
+       }
+
+       # remove unnecessary pid file
+       # also remove status FIFO, so opening it for reading won't block
+       # forever
+       unlink( $conf::pidfile, $conf::statusfile );
+
+       # replace status FIFO by a file that tells the user the daemon is down
+       if ($conf::statusfile) {
+               open( STATUSFILE, ">$conf::statusfile" )
+                       or die "Can't open $conf::statusfile: $!\n";
+               print STATUSFILE $daemon_down_text;
+               close( STATUSFILE );
+       }
+}
+
+#
+# check if daemon is running
+#
+sub daemon_running() {
+       my $pid;
+       local( *PIDFILE );
+       
+       if (open( PIDFILE, "<$conf::pidfile" )) {
+               chomp( $pid = <PIDFILE> );
+               close( PIDFILE );
+               $main::daemon_pid = $pid, return 1 if $pid && kill( 0, $pid );
+       }
+       return 0;
+}
+
+#
+# check if new keyring is available, if yes extract it
+#
+
+sub rotate_log() {
+       my( $first_date, $f1, $f2, $i );
+       local( *F );
+
+       return if !defined $main::daemon_pid || !-f $conf::logfile;
+
+       open( F, "<$conf::logfile" ) or die "Can't open $conf::logfile: $!\n";
+       while( <F> ) {
+               last if $first_date = parse_date( $_ );
+       }
+       close( F );
+       # Simply don't rotate if nothing couldn't be parsed as date -- probably
+       # the file is empty.
+       return if !$first_date;
+       # assume year-wrap if $first_date is in the future
+       $first_date -= 365*24*60*60 if $first_date > time;
+       # don't rotate if first date too young
+       return if time - $first_date < $conf::log_age*24*60*60;
+       logger( "Logfile older than $conf::log_age days, rotating\n" );
+       
+       # remove oldest log
+       $f1 = logf($conf::log_keep-1);
+       if (-f $f1) {
+               unlink( $f1 ) or warn "Can't remove $f1: $!\n";
+       }
+
+       # rename other logs
+       for( $i = $conf::log_keep-2; $i > 0; --$i ) {
+               $f1 = logf($i);
+               $f2 = logf($i+1);
+               if ($i == 0) {
+               }
+               if (-f $f1) {
+                       rename( $f1, $f2 ) or warn "Can't rename $f1 to $f2: $!\n";
+               }
+       }
+       
+       # compress newest log
+       $f1 = "$conf::logfile.0";
+       $f2 = "$conf::logfile.1.gz";
+       if (-f $f1) {
+               system $conf::gzip, "-9f", $f1
+                       and die "gzip failed on $f1 (status $?)\n";
+               rename( "$f1.gz", $f2 ) or warn "Can't rename $f1.gz to $f2: $!\n";
+       }
+
+       # rename current log and signal the daemon to open a new logfile
+       rename( $conf::logfile, $f1 );
+       kill( 1, $main::daemon_pid );
+
+       print "Rotated log files\n" if !$batchmode;
+       make_summary( 0, $first_date, $f1 )
+               if $conf::mail_summary || $conf::summary_file;
+}
+
+sub logf($) {
+       my $num = shift;
+       return sprintf( "$conf::logfile.%d.gz", $num );
+}
+
+sub parse_date($) {
+       my $date = shift;
+       my( $mon, $day, $hours, $mins, $month, $year, $secs );
+       my %month_num = ( "jan", 0, "feb", 1, "mar", 2, "apr", 3, "may", 4,
+                                         "jun", 5, "jul", 6, "aug", 7, "sep", 8, "oct", 9,
+                                         "nov", 10, "dec", 11 );
+
+       warn "Invalid date: $date\n", return 0
+               unless $date =~ /^(\w\w\w)\s+(\d+)\s+(\d+):(\d+):(\d+)\s/;
+       ($mon, $day, $hours, $mins, $secs) = ($1, $2, $3, $4, $5);
+       
+       $mon =~ tr/A-Z/a-z/;
+       return 0 if !exists $month_num{$mon};
+       $month = $month_num{$mon};
+       return timelocal( $secs, $mins, $hours, $day, $month, $main::curr_year );
+}
+
+sub make_summary($$$) {
+       my $to_stdout = shift;
+       my $startdate = shift;
+       my $file = shift;
+       my( $starts, $statusd_starts, $suspicious_files, $transient_errs,
+           $upl_failed, $success, $commands, $rm_cmds, $mv_cmds, $msg,
+           $uploader );
+       my( @pgp_fail, %transient_errs, @changes_errs, @removed_changes,
+           @already_present, @del_stray, %uploaders, %cmd_uploaders );
+       local( *F );
+       
+       if (!open( F, "<$file" )) {
+               mail( "debianqueued summary failed",
+                         "Couldn't open $file to make summary of events." );
+               return;
+       }
+
+       $starts = $statusd_starts = $suspicious_files = $transient_errs =
+               $upl_failed = $success = $commands = $rm_cmds = $mv_cmds = 0;
+       while( <F> ) {
+               $startdate = parse_date( $_ ) if !$startdate;
+               ++$starts if /daemon \(pid \d+\) started$/;
+               ++$statusd_starts if /forked status daemon/;
+               push( @pgp_fail, $1 )
+                       if /PGP signature check failed on (\S+)/;
+               ++$suspicious_files if /found suspicious filename/;
+               ++$transient_errs, ++$transient_errs{$1}
+                       if /(\S+) (doesn.t exist|is too small) \(ignored for now\)/;
+               push( @changes_errs, $1 )
+                       if (!/\((already reported|ignored for now)\)/ &&
+                               (/(\S+) doesn.t exist/ || /(\S+) has incorrect (size|md5)/)) ||
+                          /(\S+) doesn.t contain a Maintainer: field/ ||
+                          /(\S+) isn.t signed with PGP/ ||
+                          /(\S+) doesn.t mention any files/;
+               push( @removed_changes, $1 )
+                       if /(\S+) couldn.t be processed for \d+ hours and is now del/ ||
+                          /(\S+) couldn.t be uploaded for \d+ times/;
+               push( @already_present, $1 )
+                       if /(\S+) is already present on master/;
+               ++$upl_failed if /Upload to \S+ failed/;
+               ++$success, push( @{$uploaders{$2}}, $1 )
+                       if /(\S+) processed successfully \(uploader (\S*)\)$/;
+               push( @del_stray, $1 ) if /Deleted stray file (\S+)/;
+               ++$commands if /processing .*\.commands$/;
+               ++$rm_cmds if / > rm /;
+               ++$mv_cmds if / > mv /;
+               ++$cmd_uploaders{$1}
+                       if /\(command uploader (\S*)\)$/;
+       }
+       close( F );
+
+       $msg .= "Queue Daemon Summary from " . localtime($startdate) . " to " .
+                   localtime(time) . ":\n\n";
+       
+       $msg .= "Daemon started ".stimes($starts)."\n"
+               if $starts;
+       $msg .= "Status daemon restarted ".stimes($statusd_starts-$starts)."\n"
+               if $statusd_starts > $starts;
+       $msg .= @pgp_fail." job".plural_s(@pgp_fail)." failed PGP check:\n" .
+                   format_list(2,@pgp_fail)
+               if @pgp_fail; 
+       $msg .= "$suspicious_files file".plural_s($suspicious_files)." with ".
+                       "suspicious names found\n"
+               if $suspicious_files;
+       $msg .= "Detected ".$transient_errs." transient error".
+                       plural_s($transient_errs)." in .changes files:\n".
+                       format_list(2,keys %transient_errs)
+               if $transient_errs;
+       $msg .= "Detected ".@changes_errs." error".plural_s(@changes_errs).
+                   " in .changes files:\n".format_list(2,@changes_errs)
+               if @changes_errs;
+       $msg .= @removed_changes." job".plural_s(@removed_changes).
+                   " removed due to persistent errors:\n".
+                       format_list(2,@removed_changes)
+               if @removed_changes;
+       $msg .= @already_present." job".plural_s(@already_present).
+                       " were already present on master:\n".format_list(2,@already_present)
+               if @already_present;
+       $msg .= @del_stray." stray file".plural_s(@del_stray)." deleted:\n".
+                       format_list(2,@del_stray)
+               if @del_stray;
+       $msg .= "$commands command file".plural_s($commands)." processed\n"
+               if $commands;
+       $msg .= "  ($rm_cmds rm, $mv_cmds mv commands)\n"
+               if $rm_cmds || $mv_cmds;
+       $msg .= "$success job".plural_s($success)." processed successfully\n";
+
+       if ($success) {
+               $msg .= "\nPeople who used the queue:\n";
+               foreach $uploader ( keys %uploaders ) {
+                       $msg .= "  $uploader (".@{$uploaders{$uploader}}."):\n".
+                                       format_list(4,@{$uploaders{$uploader}});
+               }
+       }
+
+       if (%cmd_uploaders) {
+               $msg .= "\nPeople who used command files:\n";
+               foreach $uploader ( keys %cmd_uploaders ) {
+                       $msg .= "  $uploader ($cmd_uploaders{$uploader})\n";
+               }
+       }
+
+       if ($to_stdout) {
+               print $msg;
+       }
+       else {
+               if ($conf::mail_summary) {
+                       mail( "debianqueued summary", $msg );
+               }
+               
+               if ($conf::summary_file) {
+                       local( *F );
+                       open( F, ">>$conf::summary_file" ) or
+                               die "Cannot open $conf::summary_file for appending: $!\n";
+                       print F "\n", "-"x78, "\n", $msg;
+                       close( F );
+               }
+       }
+}
+
+sub stimes($) {
+       my $num = shift;
+       return $num == 1 ? "once" : "$num times";
+}
+
+sub plural_s($) {
+       my $num = shift;
+       return $num == 1 ? "" : "s";
+}
+
+sub format_list($@) {
+       my $indent = shift;
+       my( $i, $pos, $ret, $item, $len );
+
+       $ret = " " x $indent; $pos += $indent;
+       while( $item = shift ) {
+               $len = length($item);
+               $item .= ", ", $len += 2 if @_;
+               if ($pos+$len > LINEWIDTH) {
+                       $ret .= "\n" . " "x$indent;
+                       $pos = $indent;
+               }
+               $ret .= $item;
+               $pos += $len;
+       }
+       $ret .= "\n";
+       return $ret;
+}
+
+#
+# send mail to maintainer
+#
+sub mail($@) {
+       my $subject = shift;
+       local( *MAIL );
+       
+       open( MAIL, "|$conf::mail -s '$subject' '$conf::maintainer_mail'" )
+               or (warn( "Could not open pipe to $conf::mail: $!\n" ), return);
+       print MAIL @_;
+       print MAIL "\nGreetings,\n\n\tYour Debian queue daemon watcher\n";
+       close( MAIL )
+               or warn( "$conf::mail failed (exit status $?)\n" );
+}
+
+#
+# log something to logfile
+#
+sub logger(@) {
+       my $now = format_time();
+       local( *LOG );
+       
+       if (!open( LOG, ">>$conf::logfile" )) {
+               warn( "Can't open $conf::logfile\n" );
+               return;
+       }
+       print LOG "$now dqueued-watcher: ", @_;
+       close( LOG );
+}
+
+#
+# return current time as string
+#
+sub format_time() {
+       my $t;
+
+       # omit weekday and year for brevity
+       ($t = localtime) =~ /^\w+\s(.*)\s\d+$/;
+       return $1;
+}
+
+
+# Local Variables:
+#  tab-width: 4
+#  fill-column: 78
+# End:
diff --git a/tools/debianqueued-0.9/release-num b/tools/debianqueued-0.9/release-num
new file mode 100644 (file)
index 0000000..b63ba69
--- /dev/null
@@ -0,0 +1 @@
+0.9
diff --git a/tools/dsync-0.0/COMPILING b/tools/dsync-0.0/COMPILING
new file mode 100644 (file)
index 0000000..d2f39b8
--- /dev/null
@@ -0,0 +1,48 @@
+To compile this you need a couple things
+  - A working POSIX system with working POSIX sh, awk and sed
+  - GNU Make 3.74 or so, -- normal UNIX make will NOT work
+  - A working ANSI C++ compiler, this is not g++ 2.7.* 
+    g++ 2.8 works OK and newer egcs work well also. Nobody has tried it
+    on other compilers :<
+    You will need a properly working STL as well.
+  - A C library with the usual POSIX functions and a BSD socket layer
+
+The MD5 routine needs to know about the architecture, many of the common
+ones are in buildlib/archtable and buildlib/sizetable if your processor/host
+is not listed then just add them..
+
+This is a list of platforms and information that dsync has been compiled
+and tested on:
+
+Debian GNU Linux 2.1 'slink'
+  Linux Wakko 2.0.35 #1 Sun Nov 15 20:54:42 MST 1998 i586 unknown
+  Linux faure 2.0.35 #1 Tue Oct 30 14:31:28 CST 2018 alpha unknown  
+  g++ egcs-2.91.60
+  dsync 0.0 18/01/1999
+  - All versions work here
+  - Watch out! You get shared libraries! Use 'make ONLYSHAREDLIBS=' to 
+    disable
+  - You will want to have debiandoc-sgml and yodl installed to get
+    best results.
+    
+Sun Solaris
+  SunOS ohaton 5.6 Generic_105181-11 sun4u
+  g++ 2.8.1
+  dsync 0.0 18/01/1999
+  - The Sun I used did not have 'ar' in the path for some reason, it is
+    in /usr/ccs/bin/ar, export this before running configure or edit
+    environment.mak to fix it.
+  - libpthread seems to have some defectiveness issue with pthread_once,
+    it doesn't actually work. The code has a hack to advoid the 
+    defectiveness
+  
+HP-UX
+  HP-UX nyquist B.10.20 C 9000/780 2016574337 32-user license
+  g++ 2.8.1
+  dsync 0.0 18/01/1999
+  - I had alot of problems here initially, the utilities are very strict.
+    Things work well now.
+  - The HP-UX I used had gnu-make installed as 'gmake' this causes configure
+    to die when it does 'make dirs' I ran 'gmake dirs' by hand.
+  - There is a snprintf in the libraries someplace but it does not declare
+    it in any header, this causes all sorts of fun compile warnings
diff --git a/tools/dsync-0.0/COPYING b/tools/dsync-0.0/COPYING
new file mode 100644 (file)
index 0000000..2a0ef64
--- /dev/null
@@ -0,0 +1,4 @@
+DSync is free software; you can redistribute them and/or modify them under 
+the terms of the GNU General Public License as published by the Free Software 
+Foundation; either version 2 of the License, or (at your option) any later 
+version.
diff --git a/tools/dsync-0.0/Makefile b/tools/dsync-0.0/Makefile
new file mode 100644 (file)
index 0000000..84d3bb2
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- make -*-
+
+# This is the top level make file for APT, it recurses to each lower
+# level make file and runs it with the proper target
+ifndef NOISY
+.SILENT:
+endif
+
+.PHONY: headers library clean veryclean all binary program doc
+all headers library clean veryclean binary program doc:
+       $(MAKE) -C libdsync $@
+       $(MAKE) -C cmdline $@
+       $(MAKE) -C doc $@
+
+# Some very common aliases
+.PHONY: maintainer-clean dist-clean distclean pristine sanity 
+maintainer-clean dist-clean distclean pristine sanity: veryclean
+
+# The startup target builds the necessary configure scripts. It should
+# be used after a CVS checkout.
+CONVERTED=environment.mak include/config.h makefile
+include buildlib/configure.mak
+$(BUILDDIR)/include/config.h: buildlib/config.h.in
+$(BUILDDIR)/environment.mak: buildlib/environment.mak.in
+$(BUILDDIR)/makefile: buildlib/makefile.in
diff --git a/tools/dsync-0.0/buildlib/archtable b/tools/dsync-0.0/buildlib/archtable
new file mode 100644 (file)
index 0000000..87c0afd
--- /dev/null
@@ -0,0 +1,33 @@
+# This file contains a table of known architecture strings, with
+# things to map them to. `configure' will take the output of gcc
+# --print-libgcc-file-name, strip off leading directories up to and
+# including gcc-lib, strip off trailing /libgcc.a and trailing version
+# number directory, and then strip off everything after the first
+# hyphen.  The idea is that you're left with this bit:
+#   $ gcc --print-libgcc-file-name
+#   /usr/lib/gcc-lib/i486-linux/2.7.2/libgcc.a
+#                    ^^^^
+# This is then looked up in the table below, to find out what to map
+# it to.  If it isn't found then configure will print a warning and
+# continue.  You can override configure's ideas using --with-arch.
+# The third field is the GNU configure architecture to use with
+# this build architecture.
+#
+# This file is mirrored from dpkg.
+#
+
+i386   i386    i486
+i486   i386    i486
+i586   i386    i486
+i686   i386    i486
+pentium        i386    i486
+sparc  sparc   sparc
+alpha  alpha   alpha
+m68k   m68k    m68k
+arm    arm     arm
+armv4l  arm     arm
+powerpc        powerpc powerpc
+ppc    powerpc powerpc
+mipsel  mipsel  mipsel
+x86_64  amd64   x86_64
+
diff --git a/tools/dsync-0.0/buildlib/config.guess b/tools/dsync-0.0/buildlib/config.guess
new file mode 100755 (executable)
index 0000000..45bee13
--- /dev/null
@@ -0,0 +1,1465 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+
+timestamp='2005-04-22'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Per Bothner <per@bothner.com>.
+# Please send patches to <config-patches@gnu.org>.  Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# This script attempts to guess a canonical system name similar to
+# config.sub.  If it succeeds, it prints the system name on stdout, and
+# exits with 0.  Otherwise, it exits with 1.
+#
+# The plan is that this can be called by configure scripts if you
+# don't specify an explicit build system type.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit 0 ;;
+    --version | -v )
+       echo "$version" ; exit 0 ;;
+    --help | --h* | -h )
+       echo "$usage"; exit 0 ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )        # Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help" >&2
+       exit 1 ;;
+    * )
+       break ;;
+  esac
+done
+
+if test $# != 0; then
+  echo "$me: too many arguments$help" >&2
+  exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,)    echo "int x;" > $dummy.c ;
+       for c in cc gcc c89 c99 ; do
+         if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+            CC_FOR_BUILD="$c"; break ;
+         fi ;
+       done ;
+       if test x"$CC_FOR_BUILD" = x ; then
+         CC_FOR_BUILD=no_compiler_found ;
+       fi
+       ;;
+ ,,*)   CC_FOR_BUILD=$CC ;;
+ ,*,*)  CC_FOR_BUILD=$HOST_CC ;;
+esac ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+       PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null`  || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+    *:NetBSD:*:*)
+       # NetBSD (nbsd) targets should (where applicable) match one or
+       # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*,
+       # *-*-netbsdecoff* and *-*-netbsd*.  For targets that recently
+       # switched to ELF, *-*-netbsd* would select the old
+       # object file format.  This provides both forward
+       # compatibility and a consistent mechanism for selecting the
+       # object file format.
+       #
+       # Note: NetBSD doesn't particularly care about the vendor
+       # portion of the name.  We always set it to "unknown".
+       sysctl="sysctl -n hw.machine_arch"
+       UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
+           /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+       case "${UNAME_MACHINE_ARCH}" in
+           armeb) machine=armeb-unknown ;;
+           arm*) machine=arm-unknown ;;
+           sh3el) machine=shl-unknown ;;
+           sh3eb) machine=sh-unknown ;;
+           *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+       esac
+       # The Operating System including object format, if it has switched
+       # to ELF recently, or will in the future.
+       case "${UNAME_MACHINE_ARCH}" in
+           arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+               eval $set_cc_for_build
+               if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+                       | grep __ELF__ >/dev/null
+               then
+                   # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+                   # Return netbsd for either.  FIX?
+                   os=netbsd
+               else
+                   os=netbsdelf
+               fi
+               ;;
+           *)
+               os=netbsd
+               ;;
+       esac
+       # The OS release
+       # Debian GNU/NetBSD machines have a different userland, and
+       # thus, need a distinct triplet. However, they do not need
+       # kernel version information, so it can be replaced with a
+       # suitable tag, in the style of linux-gnu.
+       case "${UNAME_VERSION}" in
+           Debian*)
+               release='-gnu'
+               ;;
+           *)
+               release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+               ;;
+       esac
+       # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+       # contains redundant information, the shorter form:
+       # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+       echo "${machine}-${os}${release}"
+       exit 0 ;;
+    amd64:OpenBSD:*:*)
+       echo x86_64-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    amiga:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    cats:OpenBSD:*:*)
+       echo arm-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    hp300:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    luna88k:OpenBSD:*:*)
+       echo m88k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mac68k:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    macppc:OpenBSD:*:*)
+       echo powerpc-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mvme68k:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mvme88k:OpenBSD:*:*)
+       echo m88k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mvmeppc:OpenBSD:*:*)
+       echo powerpc-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    sgi:OpenBSD:*:*)
+       echo mips64-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    sun3:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    *:OpenBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    *:ekkoBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
+       exit 0 ;;
+    macppc:MirBSD:*:*)
+       echo powerppc-unknown-mirbsd${UNAME_RELEASE}
+       exit 0 ;;
+    *:MirBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
+       exit 0 ;;
+    alpha:OSF1:*:*)
+       case $UNAME_RELEASE in
+       *4.0)
+               UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+               ;;
+       *5.*)
+               UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+               ;;
+       esac
+       # According to Compaq, /usr/sbin/psrinfo has been available on
+       # OSF/1 and Tru64 systems produced since 1995.  I hope that
+       # covers most systems running today.  This code pipes the CPU
+       # types through head -n 1, so we only detect the type of CPU 0.
+       ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+       case "$ALPHA_CPU_TYPE" in
+           "EV4 (21064)")
+               UNAME_MACHINE="alpha" ;;
+           "EV4.5 (21064)")
+               UNAME_MACHINE="alpha" ;;
+           "LCA4 (21066/21068)")
+               UNAME_MACHINE="alpha" ;;
+           "EV5 (21164)")
+               UNAME_MACHINE="alphaev5" ;;
+           "EV5.6 (21164A)")
+               UNAME_MACHINE="alphaev56" ;;
+           "EV5.6 (21164PC)")
+               UNAME_MACHINE="alphapca56" ;;
+           "EV5.7 (21164PC)")
+               UNAME_MACHINE="alphapca57" ;;
+           "EV6 (21264)")
+               UNAME_MACHINE="alphaev6" ;;
+           "EV6.7 (21264A)")
+               UNAME_MACHINE="alphaev67" ;;
+           "EV6.8CB (21264C)")
+               UNAME_MACHINE="alphaev68" ;;
+           "EV6.8AL (21264B)")
+               UNAME_MACHINE="alphaev68" ;;
+           "EV6.8CX (21264D)")
+               UNAME_MACHINE="alphaev68" ;;
+           "EV6.9A (21264/EV69A)")
+               UNAME_MACHINE="alphaev69" ;;
+           "EV7 (21364)")
+               UNAME_MACHINE="alphaev7" ;;
+           "EV7.9 (21364A)")
+               UNAME_MACHINE="alphaev79" ;;
+       esac
+       # A Pn.n version is a patched version.
+       # A Vn.n version is a released version.
+       # A Tn.n version is a released field test version.
+       # A Xn.n version is an unreleased experimental baselevel.
+       # 1.2 uses "1.2" for uname -r.
+       echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+       exit 0 ;;
+    Alpha\ *:Windows_NT*:*)
+       # How do we know it's Interix rather than the generic POSIX subsystem?
+       # Should we change UNAME_MACHINE based on the output of uname instead
+       # of the specific Alpha model?
+       echo alpha-pc-interix
+       exit 0 ;;
+    21064:Windows_NT:50:3)
+       echo alpha-dec-winnt3.5
+       exit 0 ;;
+    Amiga*:UNIX_System_V:4.0:*)
+       echo m68k-unknown-sysv4
+       exit 0;;
+    *:[Aa]miga[Oo][Ss]:*:*)
+       echo ${UNAME_MACHINE}-unknown-amigaos
+       exit 0 ;;
+    *:[Mm]orph[Oo][Ss]:*:*)
+       echo ${UNAME_MACHINE}-unknown-morphos
+       exit 0 ;;
+    *:OS/390:*:*)
+       echo i370-ibm-openedition
+       exit 0 ;;
+    *:z/VM:*:*)
+       echo s390-ibm-zvmoe
+       exit 0 ;;
+    *:OS400:*:*)
+        echo powerpc-ibm-os400
+       exit 0 ;;
+    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+       echo arm-acorn-riscix${UNAME_RELEASE}
+       exit 0;;
+    SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+       echo hppa1.1-hitachi-hiuxmpp
+       exit 0;;
+    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+       # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+       if test "`(/bin/universe) 2>/dev/null`" = att ; then
+               echo pyramid-pyramid-sysv3
+       else
+               echo pyramid-pyramid-bsd
+       fi
+       exit 0 ;;
+    NILE*:*:*:dcosx)
+       echo pyramid-pyramid-svr4
+       exit 0 ;;
+    DRS?6000:unix:4.0:6*)
+       echo sparc-icl-nx6
+       exit 0 ;;
+    DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+       case `/usr/bin/uname -p` in
+           sparc) echo sparc-icl-nx7 && exit 0 ;;
+       esac ;;
+    sun4H:SunOS:5.*:*)
+       echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+       echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    i86pc:SunOS:5.*:*)
+       echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    sun4*:SunOS:6*:*)
+       # According to config.sub, this is the proper way to canonicalize
+       # SunOS6.  Hard to guess exactly what SunOS6 will be like, but
+       # it's likely to be more like Solaris than SunOS4.
+       echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    sun4*:SunOS:*:*)
+       case "`/usr/bin/arch -k`" in
+           Series*|S4*)
+               UNAME_RELEASE=`uname -v`
+               ;;
+       esac
+       # Japanese Language versions have a version number like `4.1.3-JL'.
+       echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+       exit 0 ;;
+    sun3*:SunOS:*:*)
+       echo m68k-sun-sunos${UNAME_RELEASE}
+       exit 0 ;;
+    sun*:*:4.2BSD:*)
+       UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+       test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+       case "`/bin/arch`" in
+           sun3)
+               echo m68k-sun-sunos${UNAME_RELEASE}
+               ;;
+           sun4)
+               echo sparc-sun-sunos${UNAME_RELEASE}
+               ;;
+       esac
+       exit 0 ;;
+    aushp:SunOS:*:*)
+       echo sparc-auspex-sunos${UNAME_RELEASE}
+       exit 0 ;;
+    # The situation for MiNT is a little confusing.  The machine name
+    # can be virtually everything (everything which is not
+    # "atarist" or "atariste" at least should have a processor
+    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
+    # to the lowercase version "mint" (or "freemint").  Finally
+    # the system name "TOS" denotes a system which is actually not
+    # MiNT.  But MiNT is downward compatible to TOS, so this should
+    # be no problem.
+    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+       exit 0 ;;
+    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+       echo m68k-atari-mint${UNAME_RELEASE}
+        exit 0 ;;
+    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+       exit 0 ;;
+    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+        echo m68k-milan-mint${UNAME_RELEASE}
+        exit 0 ;;
+    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+        echo m68k-hades-mint${UNAME_RELEASE}
+        exit 0 ;;
+    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+        echo m68k-unknown-mint${UNAME_RELEASE}
+        exit 0 ;;
+    m68k:machten:*:*)
+       echo m68k-apple-machten${UNAME_RELEASE}
+       exit 0 ;;
+    powerpc:machten:*:*)
+       echo powerpc-apple-machten${UNAME_RELEASE}
+       exit 0 ;;
+    RISC*:Mach:*:*)
+       echo mips-dec-mach_bsd4.3
+       exit 0 ;;
+    RISC*:ULTRIX:*:*)
+       echo mips-dec-ultrix${UNAME_RELEASE}
+       exit 0 ;;
+    VAX*:ULTRIX*:*:*)
+       echo vax-dec-ultrix${UNAME_RELEASE}
+       exit 0 ;;
+    2020:CLIX:*:* | 2430:CLIX:*:*)
+       echo clipper-intergraph-clix${UNAME_RELEASE}
+       exit 0 ;;
+    mips:*:*:UMIPS | mips:*:*:RISCos)
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h>  /* for printf() prototype */
+       int main (int argc, char *argv[]) {
+#else
+       int main (argc, argv) int argc; char *argv[]; {
+#endif
+       #if defined (host_mips) && defined (MIPSEB)
+       #if defined (SYSTYPE_SYSV)
+         printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+       #endif
+       #if defined (SYSTYPE_SVR4)
+         printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+       #endif
+       #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+         printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+       #endif
+       #endif
+         exit (-1);
+       }
+EOF
+       $CC_FOR_BUILD -o $dummy $dummy.c \
+         && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \
+         && exit 0
+       echo mips-mips-riscos${UNAME_RELEASE}
+       exit 0 ;;
+    Motorola:PowerMAX_OS:*:*)
+       echo powerpc-motorola-powermax
+       exit 0 ;;
+    Motorola:*:4.3:PL8-*)
+       echo powerpc-harris-powermax
+       exit 0 ;;
+    Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+       echo powerpc-harris-powermax
+       exit 0 ;;
+    Night_Hawk:Power_UNIX:*:*)
+       echo powerpc-harris-powerunix
+       exit 0 ;;
+    m88k:CX/UX:7*:*)
+       echo m88k-harris-cxux7
+       exit 0 ;;
+    m88k:*:4*:R4*)
+       echo m88k-motorola-sysv4
+       exit 0 ;;
+    m88k:*:3*:R3*)
+       echo m88k-motorola-sysv3
+       exit 0 ;;
+    AViiON:dgux:*:*)
+        # DG/UX returns AViiON for all architectures
+        UNAME_PROCESSOR=`/usr/bin/uname -p`
+       if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+       then
+           if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+              [ ${TARGET_BINARY_INTERFACE}x = x ]
+           then
+               echo m88k-dg-dgux${UNAME_RELEASE}
+           else
+               echo m88k-dg-dguxbcs${UNAME_RELEASE}
+           fi
+       else
+           echo i586-dg-dgux${UNAME_RELEASE}
+       fi
+       exit 0 ;;
+    M88*:DolphinOS:*:*)        # DolphinOS (SVR3)
+       echo m88k-dolphin-sysv3
+       exit 0 ;;
+    M88*:*:R3*:*)
+       # Delta 88k system running SVR3
+       echo m88k-motorola-sysv3
+       exit 0 ;;
+    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+       echo m88k-tektronix-sysv3
+       exit 0 ;;
+    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+       echo m68k-tektronix-bsd
+       exit 0 ;;
+    *:IRIX*:*:*)
+       echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+       exit 0 ;;
+    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+       echo romp-ibm-aix      # uname -m gives an 8 hex-code CPU id
+       exit 0 ;;              # Note that: echo "'`uname -s`'" gives 'AIX '
+    i*86:AIX:*:*)
+       echo i386-ibm-aix
+       exit 0 ;;
+    ia64:AIX:*:*)
+       if [ -x /usr/bin/oslevel ] ; then
+               IBM_REV=`/usr/bin/oslevel`
+       else
+               IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+       fi
+       echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+       exit 0 ;;
+    *:AIX:2:3)
+       if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+               eval $set_cc_for_build
+               sed 's/^                //' << EOF >$dummy.c
+               #include <sys/systemcfg.h>
+
+               main()
+                       {
+                       if (!__power_pc())
+                               exit(1);
+                       puts("powerpc-ibm-aix3.2.5");
+                       exit(0);
+                       }
+EOF
+               $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0
+               echo rs6000-ibm-aix3.2.5
+       elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+               echo rs6000-ibm-aix3.2.4
+       else
+               echo rs6000-ibm-aix3.2
+       fi
+       exit 0 ;;
+    *:AIX:*:[45])
+       IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+       if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+               IBM_ARCH=rs6000
+       else
+               IBM_ARCH=powerpc
+       fi
+       if [ -x /usr/bin/oslevel ] ; then
+               IBM_REV=`/usr/bin/oslevel`
+       else
+               IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+       fi
+       echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+       exit 0 ;;
+    *:AIX:*:*)
+       echo rs6000-ibm-aix
+       exit 0 ;;
+    ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+       echo romp-ibm-bsd4.4
+       exit 0 ;;
+    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
+       echo romp-ibm-bsd${UNAME_RELEASE}   # 4.3 with uname added to
+       exit 0 ;;                           # report: romp-ibm BSD 4.3
+    *:BOSX:*:*)
+       echo rs6000-bull-bosx
+       exit 0 ;;
+    DPX/2?00:B.O.S.:*:*)
+       echo m68k-bull-sysv3
+       exit 0 ;;
+    9000/[34]??:4.3bsd:1.*:*)
+       echo m68k-hp-bsd
+       exit 0 ;;
+    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+       echo m68k-hp-bsd4.4
+       exit 0 ;;
+    9000/[34678]??:HP-UX:*:*)
+       HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+       case "${UNAME_MACHINE}" in
+           9000/31? )            HP_ARCH=m68000 ;;
+           9000/[34]?? )         HP_ARCH=m68k ;;
+           9000/[678][0-9][0-9])
+               if [ -x /usr/bin/getconf ]; then
+                   sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+                    sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+                    case "${sc_cpu_version}" in
+                      523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
+                      528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+                      532)                      # CPU_PA_RISC2_0
+                        case "${sc_kernel_bits}" in
+                          32) HP_ARCH="hppa2.0n" ;;
+                          64) HP_ARCH="hppa2.0w" ;;
+                         '') HP_ARCH="hppa2.0" ;;   # HP-UX 10.20
+                        esac ;;
+                    esac
+               fi
+               if [ "${HP_ARCH}" = "" ]; then
+                   eval $set_cc_for_build
+                   sed 's/^              //' << EOF >$dummy.c
+
+              #define _HPUX_SOURCE
+              #include <stdlib.h>
+              #include <unistd.h>
+
+              int main ()
+              {
+              #if defined(_SC_KERNEL_BITS)
+                  long bits = sysconf(_SC_KERNEL_BITS);
+              #endif
+                  long cpu  = sysconf (_SC_CPU_VERSION);
+
+                  switch (cpu)
+               {
+               case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+               case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+               case CPU_PA_RISC2_0:
+              #if defined(_SC_KERNEL_BITS)
+                   switch (bits)
+                       {
+                       case 64: puts ("hppa2.0w"); break;
+                       case 32: puts ("hppa2.0n"); break;
+                       default: puts ("hppa2.0"); break;
+                       } break;
+              #else  /* !defined(_SC_KERNEL_BITS) */
+                   puts ("hppa2.0"); break;
+              #endif
+               default: puts ("hppa1.0"); break;
+               }
+                  exit (0);
+              }
+EOF
+                   (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+                   test -z "$HP_ARCH" && HP_ARCH=hppa
+               fi ;;
+       esac
+       if [ ${HP_ARCH} = "hppa2.0w" ]
+       then
+           # avoid double evaluation of $set_cc_for_build
+           test -n "$CC_FOR_BUILD" || eval $set_cc_for_build
+           if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null
+           then
+               HP_ARCH="hppa2.0w"
+           else
+               HP_ARCH="hppa64"
+           fi
+       fi
+       echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+       exit 0 ;;
+    ia64:HP-UX:*:*)
+       HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+       echo ia64-hp-hpux${HPUX_REV}
+       exit 0 ;;
+    3050*:HI-UX:*:*)
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+       #include <unistd.h>
+       int
+       main ()
+       {
+         long cpu = sysconf (_SC_CPU_VERSION);
+         /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+            true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
+            results, however.  */
+         if (CPU_IS_PA_RISC (cpu))
+           {
+             switch (cpu)
+               {
+                 case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+                 case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+                 case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+                 default: puts ("hppa-hitachi-hiuxwe2"); break;
+               }
+           }
+         else if (CPU_IS_HP_MC68K (cpu))
+           puts ("m68k-hitachi-hiuxwe2");
+         else puts ("unknown-hitachi-hiuxwe2");
+         exit (0);
+       }
+EOF
+       $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0
+       echo unknown-hitachi-hiuxwe2
+       exit 0 ;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+       echo hppa1.1-hp-bsd
+       exit 0 ;;
+    9000/8??:4.3bsd:*:*)
+       echo hppa1.0-hp-bsd
+       exit 0 ;;
+    *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+       echo hppa1.0-hp-mpeix
+       exit 0 ;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+       echo hppa1.1-hp-osf
+       exit 0 ;;
+    hp8??:OSF1:*:*)
+       echo hppa1.0-hp-osf
+       exit 0 ;;
+    i*86:OSF1:*:*)
+       if [ -x /usr/sbin/sysversion ] ; then
+           echo ${UNAME_MACHINE}-unknown-osf1mk
+       else
+           echo ${UNAME_MACHINE}-unknown-osf1
+       fi
+       exit 0 ;;
+    parisc*:Lites*:*:*)
+       echo hppa1.1-hp-lites
+       exit 0 ;;
+    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+       echo c1-convex-bsd
+        exit 0 ;;
+    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+       if getsysinfo -f scalar_acc
+       then echo c32-convex-bsd
+       else echo c2-convex-bsd
+       fi
+        exit 0 ;;
+    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+       echo c34-convex-bsd
+        exit 0 ;;
+    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+       echo c38-convex-bsd
+        exit 0 ;;
+    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+       echo c4-convex-bsd
+        exit 0 ;;
+    CRAY*Y-MP:*:*:*)
+       echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit 0 ;;
+    CRAY*[A-Z]90:*:*:*)
+       echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+       | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+             -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+             -e 's/\.[^.]*$/.X/'
+       exit 0 ;;
+    CRAY*TS:*:*:*)
+       echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit 0 ;;
+    CRAY*T3E:*:*:*)
+       echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit 0 ;;
+    CRAY*SV1:*:*:*)
+       echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit 0 ;;
+    *:UNICOS/mp:*:*)
+       echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit 0 ;;
+    F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+       FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+        FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+        FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+        echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+        exit 0 ;;
+    5000:UNIX_System_V:4.*:*)
+        FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+        FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
+        echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+       exit 0 ;;
+    i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+       echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+       exit 0 ;;
+    sparc*:BSD/OS:*:*)
+       echo sparc-unknown-bsdi${UNAME_RELEASE}
+       exit 0 ;;
+    *:BSD/OS:*:*)
+       echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+       exit 0 ;;
+    *:FreeBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+       exit 0 ;;
+    i*:CYGWIN*:*)
+       echo ${UNAME_MACHINE}-pc-cygwin
+       exit 0 ;;
+    i*:MINGW*:*)
+       echo ${UNAME_MACHINE}-pc-mingw32
+       exit 0 ;;
+    i*:PW*:*)
+       echo ${UNAME_MACHINE}-pc-pw32
+       exit 0 ;;
+    x86:Interix*:[34]*)
+       echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//'
+       exit 0 ;;
+    [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+       echo i${UNAME_MACHINE}-pc-mks
+       exit 0 ;;
+    i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+       # How do we know it's Interix rather than the generic POSIX subsystem?
+       # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+       # UNAME_MACHINE based on the output of uname instead of i386?
+       echo i586-pc-interix
+       exit 0 ;;
+    i*:UWIN*:*)
+       echo ${UNAME_MACHINE}-pc-uwin
+       exit 0 ;;
+    amd64:CYGWIN*:*:*)
+       echo x86_64-unknown-cygwin
+       exit 0 ;;
+    p*:CYGWIN*:*)
+       echo powerpcle-unknown-cygwin
+       exit 0 ;;
+    prep*:SunOS:5.*:*)
+       echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    *:GNU:*:*)
+       # the GNU system
+       echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+       exit 0 ;;
+    *:GNU/*:*:*)
+       # other systems with GNU libc and userland
+       echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu
+       exit 0 ;;
+    i*86:Minix:*:*)
+       echo ${UNAME_MACHINE}-pc-minix
+       exit 0 ;;
+    arm*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit 0 ;;
+    cris:Linux:*:*)
+       echo cris-axis-linux-gnu
+       exit 0 ;;
+    crisv32:Linux:*:*)
+       echo crisv32-axis-linux-gnu
+       exit 0 ;;
+    frv:Linux:*:*)
+       echo frv-unknown-linux-gnu
+       exit 0 ;;
+    ia64:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit 0 ;;
+    m32r*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit 0 ;;
+    m68*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit 0 ;;
+    mips:Linux:*:*)
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+       #undef CPU
+       #undef mips
+       #undef mipsel
+       #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+       CPU=mipsel
+       #else
+       #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+       CPU=mips
+       #else
+       CPU=
+       #endif
+       #endif
+EOF
+       eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=`
+       test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0
+       ;;
+    mips64:Linux:*:*)
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+       #undef CPU
+       #undef mips64
+       #undef mips64el
+       #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+       CPU=mips64el
+       #else
+       #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+       CPU=mips64
+       #else
+       CPU=
+       #endif
+       #endif
+EOF
+       eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=`
+       test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0
+       ;;
+    ppc:Linux:*:*)
+       echo powerpc-unknown-linux-gnu
+       exit 0 ;;
+    ppc64:Linux:*:*)
+       echo powerpc64-unknown-linux-gnu
+       exit 0 ;;
+    alpha:Linux:*:*)
+       case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+         EV5)   UNAME_MACHINE=alphaev5 ;;
+         EV56)  UNAME_MACHINE=alphaev56 ;;
+         PCA56) UNAME_MACHINE=alphapca56 ;;
+         PCA57) UNAME_MACHINE=alphapca56 ;;
+         EV6)   UNAME_MACHINE=alphaev6 ;;
+         EV67)  UNAME_MACHINE=alphaev67 ;;
+         EV68*) UNAME_MACHINE=alphaev68 ;;
+        esac
+       objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null
+       if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi
+       echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC}
+       exit 0 ;;
+    parisc:Linux:*:* | hppa:Linux:*:*)
+       # Look for CPU level
+       case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+         PA7*) echo hppa1.1-unknown-linux-gnu ;;
+         PA8*) echo hppa2.0-unknown-linux-gnu ;;
+         *)    echo hppa-unknown-linux-gnu ;;
+       esac
+       exit 0 ;;
+    parisc64:Linux:*:* | hppa64:Linux:*:*)
+       echo hppa64-unknown-linux-gnu
+       exit 0 ;;
+    s390:Linux:*:* | s390x:Linux:*:*)
+       echo ${UNAME_MACHINE}-ibm-linux
+       exit 0 ;;
+    sh64*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit 0 ;;
+    sh*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit 0 ;;
+    sparc:Linux:*:* | sparc64:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit 0 ;;
+    x86_64:Linux:*:*)
+       echo x86_64-unknown-linux-gnu
+       exit 0 ;;
+    i*86:Linux:*:*)
+       # The BFD linker knows what the default object file format is, so
+       # first see if it will tell us. cd to the root directory to prevent
+       # problems with other programs or directories called `ld' in the path.
+       # Set LC_ALL=C to ensure ld outputs messages in English.
+       ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \
+                        | sed -ne '/supported targets:/!d
+                                   s/[         ][      ]*/ /g
+                                   s/.*supported targets: *//
+                                   s/ .*//
+                                   p'`
+        case "$ld_supported_targets" in
+         elf32-i386)
+               TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu"
+               ;;
+         a.out-i386-linux)
+               echo "${UNAME_MACHINE}-pc-linux-gnuaout"
+               exit 0 ;;
+         coff-i386)
+               echo "${UNAME_MACHINE}-pc-linux-gnucoff"
+               exit 0 ;;
+         "")
+               # Either a pre-BFD a.out linker (linux-gnuoldld) or
+               # one that does not give us useful --help.
+               echo "${UNAME_MACHINE}-pc-linux-gnuoldld"
+               exit 0 ;;
+       esac
+       # Determine whether the default compiler is a.out or elf
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+       #include <features.h>
+       #ifdef __ELF__
+       # ifdef __GLIBC__
+       #  if __GLIBC__ >= 2
+       LIBC=gnu
+       #  else
+       LIBC=gnulibc1
+       #  endif
+       # else
+       LIBC=gnulibc1
+       # endif
+       #else
+       #ifdef __INTEL_COMPILER
+       LIBC=gnu
+       #else
+       LIBC=gnuaout
+       #endif
+       #endif
+       #ifdef __dietlibc__
+       LIBC=dietlibc
+       #endif
+EOF
+       eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=`
+       test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0
+       test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0
+       ;;
+    i*86:DYNIX/ptx:4*:*)
+       # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+       # earlier versions are messed up and put the nodename in both
+       # sysname and nodename.
+       echo i386-sequent-sysv4
+       exit 0 ;;
+    i*86:UNIX_SV:4.2MP:2.*)
+        # Unixware is an offshoot of SVR4, but it has its own version
+        # number series starting with 2...
+        # I am not positive that other SVR4 systems won't match this,
+       # I just have to hope.  -- rms.
+        # Use sysv4.2uw... so that sysv4* matches it.
+       echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+       exit 0 ;;
+    i*86:OS/2:*:*)
+       # If we were able to find `uname', then EMX Unix compatibility
+       # is probably installed.
+       echo ${UNAME_MACHINE}-pc-os2-emx
+       exit 0 ;;
+    i*86:XTS-300:*:STOP)
+       echo ${UNAME_MACHINE}-unknown-stop
+       exit 0 ;;
+    i*86:atheos:*:*)
+       echo ${UNAME_MACHINE}-unknown-atheos
+       exit 0 ;;
+       i*86:syllable:*:*)
+       echo ${UNAME_MACHINE}-pc-syllable
+       exit 0 ;;
+    i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*)
+       echo i386-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    i*86:*DOS:*:*)
+       echo ${UNAME_MACHINE}-pc-msdosdjgpp
+       exit 0 ;;
+    i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+       UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+       if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+               echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+       else
+               echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+       fi
+       exit 0 ;;
+    i*86:*:5:[78]*)
+       case `/bin/uname -X | grep "^Machine"` in
+           *486*)           UNAME_MACHINE=i486 ;;
+           *Pentium)        UNAME_MACHINE=i586 ;;
+           *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+       esac
+       echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+       exit 0 ;;
+    i*86:*:3.2:*)
+       if test -f /usr/options/cb.name; then
+               UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+               echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+       elif /bin/uname -X 2>/dev/null >/dev/null ; then
+               UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+               (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+               (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+                       && UNAME_MACHINE=i586
+               (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+                       && UNAME_MACHINE=i686
+               (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+                       && UNAME_MACHINE=i686
+               echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+       else
+               echo ${UNAME_MACHINE}-pc-sysv32
+       fi
+       exit 0 ;;
+    pc:*:*:*)
+       # Left here for compatibility:
+        # uname -m prints for DJGPP always 'pc', but it prints nothing about
+        # the processor, so we play safe by assuming i386.
+       echo i386-pc-msdosdjgpp
+        exit 0 ;;
+    Intel:Mach:3*:*)
+       echo i386-pc-mach3
+       exit 0 ;;
+    paragon:*:*:*)
+       echo i860-intel-osf1
+       exit 0 ;;
+    i860:*:4.*:*) # i860-SVR4
+       if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+         echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+       else # Add other i860-SVR4 vendors below as they are discovered.
+         echo i860-unknown-sysv${UNAME_RELEASE}  # Unknown i860-SVR4
+       fi
+       exit 0 ;;
+    mini*:CTIX:SYS*5:*)
+       # "miniframe"
+       echo m68010-convergent-sysv
+       exit 0 ;;
+    mc68k:UNIX:SYSTEM5:3.51m)
+       echo m68k-convergent-sysv
+       exit 0 ;;
+    M680?0:D-NIX:5.3:*)
+       echo m68k-diab-dnix
+       exit 0 ;;
+    M68*:*:R3V[5678]*:*)
+       test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;;
+    3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+       OS_REL=''
+       test -r /etc/.relid \
+       && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+         && echo i486-ncr-sysv4.3${OS_REL} && exit 0
+       /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+         && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;;
+    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+        /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+          && echo i486-ncr-sysv4 && exit 0 ;;
+    m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+       echo m68k-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    mc68030:UNIX_System_V:4.*:*)
+       echo m68k-atari-sysv4
+       exit 0 ;;
+    TSUNAMI:LynxOS:2.*:*)
+       echo sparc-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    rs6000:LynxOS:2.*:*)
+       echo rs6000-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*)
+       echo powerpc-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    SM[BE]S:UNIX_SV:*:*)
+       echo mips-dde-sysv${UNAME_RELEASE}
+       exit 0 ;;
+    RM*:ReliantUNIX-*:*:*)
+       echo mips-sni-sysv4
+       exit 0 ;;
+    RM*:SINIX-*:*:*)
+       echo mips-sni-sysv4
+       exit 0 ;;
+    *:SINIX-*:*:*)
+       if uname -p 2>/dev/null >/dev/null ; then
+               UNAME_MACHINE=`(uname -p) 2>/dev/null`
+               echo ${UNAME_MACHINE}-sni-sysv4
+       else
+               echo ns32k-sni-sysv
+       fi
+       exit 0 ;;
+    PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+                      # says <Richard.M.Bartel@ccMail.Census.GOV>
+        echo i586-unisys-sysv4
+        exit 0 ;;
+    *:UNIX_System_V:4*:FTX*)
+       # From Gerald Hewes <hewes@openmarket.com>.
+       # How about differentiating between stratus architectures? -djm
+       echo hppa1.1-stratus-sysv4
+       exit 0 ;;
+    *:*:*:FTX*)
+       # From seanf@swdc.stratus.com.
+       echo i860-stratus-sysv4
+       exit 0 ;;
+    i*86:VOS:*:*)
+       # From Paul.Green@stratus.com.
+       echo ${UNAME_MACHINE}-stratus-vos
+       exit 0 ;;
+    *:VOS:*:*)
+       # From Paul.Green@stratus.com.
+       echo hppa1.1-stratus-vos
+       exit 0 ;;
+    mc68*:A/UX:*:*)
+       echo m68k-apple-aux${UNAME_RELEASE}
+       exit 0 ;;
+    news*:NEWS-OS:6*:*)
+       echo mips-sony-newsos6
+       exit 0 ;;
+    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+       if [ -d /usr/nec ]; then
+               echo mips-nec-sysv${UNAME_RELEASE}
+       else
+               echo mips-unknown-sysv${UNAME_RELEASE}
+       fi
+        exit 0 ;;
+    BeBox:BeOS:*:*)    # BeOS running on hardware made by Be, PPC only.
+       echo powerpc-be-beos
+       exit 0 ;;
+    BeMac:BeOS:*:*)    # BeOS running on Mac or Mac clone, PPC only.
+       echo powerpc-apple-beos
+       exit 0 ;;
+    BePC:BeOS:*:*)     # BeOS running on Intel PC compatible.
+       echo i586-pc-beos
+       exit 0 ;;
+    SX-4:SUPER-UX:*:*)
+       echo sx4-nec-superux${UNAME_RELEASE}
+       exit 0 ;;
+    SX-5:SUPER-UX:*:*)
+       echo sx5-nec-superux${UNAME_RELEASE}
+       exit 0 ;;
+    SX-6:SUPER-UX:*:*)
+       echo sx6-nec-superux${UNAME_RELEASE}
+       exit 0 ;;
+    Power*:Rhapsody:*:*)
+       echo powerpc-apple-rhapsody${UNAME_RELEASE}
+       exit 0 ;;
+    *:Rhapsody:*:*)
+       echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+       exit 0 ;;
+    *:Darwin:*:*)
+       UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
+       case $UNAME_PROCESSOR in
+           *86) UNAME_PROCESSOR=i686 ;;
+           unknown) UNAME_PROCESSOR=powerpc ;;
+       esac
+       echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+       exit 0 ;;
+    *:procnto*:*:* | *:QNX:[0123456789]*:*)
+       UNAME_PROCESSOR=`uname -p`
+       if test "$UNAME_PROCESSOR" = "x86"; then
+               UNAME_PROCESSOR=i386
+               UNAME_MACHINE=pc
+       fi
+       echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+       exit 0 ;;
+    *:QNX:*:4*)
+       echo i386-pc-qnx
+       exit 0 ;;
+    NSE-?:NONSTOP_KERNEL:*:*)
+       echo nse-tandem-nsk${UNAME_RELEASE}
+       exit 0 ;;
+    NSR-?:NONSTOP_KERNEL:*:*)
+       echo nsr-tandem-nsk${UNAME_RELEASE}
+       exit 0 ;;
+    *:NonStop-UX:*:*)
+       echo mips-compaq-nonstopux
+       exit 0 ;;
+    BS2000:POSIX*:*:*)
+       echo bs2000-siemens-sysv
+       exit 0 ;;
+    DS/*:UNIX_System_V:*:*)
+       echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+       exit 0 ;;
+    *:Plan9:*:*)
+       # "uname -m" is not consistent, so use $cputype instead. 386
+       # is converted to i386 for consistency with other x86
+       # operating systems.
+       if test "$cputype" = "386"; then
+           UNAME_MACHINE=i386
+       else
+           UNAME_MACHINE="$cputype"
+       fi
+       echo ${UNAME_MACHINE}-unknown-plan9
+       exit 0 ;;
+    *:TOPS-10:*:*)
+       echo pdp10-unknown-tops10
+       exit 0 ;;
+    *:TENEX:*:*)
+       echo pdp10-unknown-tenex
+       exit 0 ;;
+    KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+       echo pdp10-dec-tops20
+       exit 0 ;;
+    XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+       echo pdp10-xkl-tops20
+       exit 0 ;;
+    *:TOPS-20:*:*)
+       echo pdp10-unknown-tops20
+       exit 0 ;;
+    *:ITS:*:*)
+       echo pdp10-unknown-its
+       exit 0 ;;
+    SEI:*:*:SEIUX)
+        echo mips-sei-seiux${UNAME_RELEASE}
+       exit 0 ;;
+    *:DragonFly:*:*)
+       echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+       exit 0 ;;
+    *:*VMS:*:*)
+       UNAME_MACHINE=`(uname -p) 2>/dev/null`
+       case "${UNAME_MACHINE}" in
+           A*) echo alpha-dec-vms && exit 0 ;;
+           I*) echo ia64-dec-vms && exit 0 ;;
+           V*) echo vax-dec-vms && exit 0 ;;
+       esac ;;
+    *:XENIX:*:SysV)
+       echo i386-pc-xenix
+       exit 0 ;;
+esac
+
+#echo '(No uname command or uname output not recognized.)' 1>&2
+#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2
+
+eval $set_cc_for_build
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+          "4"
+#else
+         ""
+#endif
+         ); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+  printf ("arm-acorn-riscix"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+  printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+    struct utsname un;
+
+    uname(&un);
+
+    if (strncmp(un.version, "V2", 2) == 0) {
+       printf ("i386-sequent-ptx2\n"); exit (0);
+    }
+    if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+       printf ("i386-sequent-ptx1\n"); exit (0);
+    }
+    printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+# if !defined (ultrix)
+#  include <sys/param.h>
+#  if defined (BSD)
+#   if BSD == 43
+      printf ("vax-dec-bsd4.3\n"); exit (0);
+#   else
+#    if BSD == 199006
+      printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#    else
+      printf ("vax-dec-bsd\n"); exit (0);
+#    endif
+#   endif
+#  else
+    printf ("vax-dec-bsd\n"); exit (0);
+#  endif
+# else
+    printf ("vax-dec-ultrix\n"); exit (0);
+# endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+    case `getsysinfo -f cpu_type` in
+    c1*)
+       echo c1-convex-bsd
+       exit 0 ;;
+    c2*)
+       if getsysinfo -f scalar_acc
+       then echo c32-convex-bsd
+       else echo c2-convex-bsd
+       fi
+       exit 0 ;;
+    c34*)
+       echo c34-convex-bsd
+       exit 0 ;;
+    c38*)
+       echo c38-convex-bsd
+       exit 0 ;;
+    c4*)
+       echo c4-convex-bsd
+       exit 0 ;;
+    esac
+fi
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script, last modified $timestamp, has failed to recognize
+the operating system you are using. It is advised that you
+download the most up to date version of the config scripts from
+
+  http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.guess
+and
+  http://savannah.gnu.org/cgi-bin/viewcvs/*checkout*/config/config/config.sub
+
+If the version you run ($0) is already up to date, please
+send the following data and any information you think might be
+pertinent to <config-patches@gnu.org> in order to provide the needed
+information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo               = `(hostinfo) 2>/dev/null`
+/bin/universe          = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch              = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM  = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/tools/dsync-0.0/buildlib/config.h.in b/tools/dsync-0.0/buildlib/config.h.in
new file mode 100644 (file)
index 0000000..f882ca2
--- /dev/null
@@ -0,0 +1,37 @@
+/* Define if your processor stores words with the most significant
+   byte first (like Motorola and SPARC, unlike Intel and VAX).  */
+#undef WORDS_BIGENDIAN
+
+/* The number of bytes in a usigned char.  */
+#undef SIZEOF_CHAR
+
+/* The number of bytes in a unsigned int.  */
+#undef SIZEOF_INT
+
+/* The number of bytes in a unsigned long.  */
+#undef SIZEOF_LONG
+
+/* The number of bytes in a unsigned short.  */
+#undef SIZEOF_SHORT
+
+/* Define if we have libgpm. */
+#undef HAVE_LIBGPM
+
+/* Define if we have the SLang library from Davis. */
+#undef HAVE_LIBSLANG    
+
+/* Define if we have the X11 windowing system. */
+#undef HAVE_X11
+
+/* Define if we have enabled pthread support */
+#undef HAVE_PTHREAD
+
+/* Define the architecture name string */
+#undef ARCHITECTURE
+
+/* The version number string */
+#undef VERSION
+
+/* The package name string */
+#undef PACKAGE
+
diff --git a/tools/dsync-0.0/buildlib/config.sub b/tools/dsync-0.0/buildlib/config.sub
new file mode 100755 (executable)
index 0000000..87a1ee4
--- /dev/null
@@ -0,0 +1,1569 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+
+timestamp='2005-04-22'
+
+# This file is (in principle) common to ALL GNU software.
+# The presence of a machine in this file suggests that SOME GNU software
+# can handle that machine.  It does not imply ALL GNU software can.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Please send patches to <config-patches@gnu.org>.  Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support.  The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+#      CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+#      CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS
+       $0 [OPTION] ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit 0 ;;
+    --version | -v )
+       echo "$version" ; exit 0 ;;
+    --help | --h* | -h )
+       echo "$usage"; exit 0 ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )        # Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help"
+       exit 1 ;;
+
+    *local*)
+       # First pass through any local machine types.
+       echo $1
+       exit 0;;
+
+    * )
+       break ;;
+  esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+    exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+    exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+  nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \
+  kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*)
+    os=-$maybe_os
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+    ;;
+  *)
+    basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+    if [ $basic_machine != $1 ]
+    then os=`echo $1 | sed 's/.*-/-/'`
+    else os=; fi
+    ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work.  We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+       -sun*os*)
+               # Prevent following clause from handling this invalid input.
+               ;;
+       -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+       -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+       -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+       -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+       -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+       -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+       -apple | -axis | -knuth | -cray)
+               os=
+               basic_machine=$1
+               ;;
+       -sim | -cisco | -oki | -wec | -winbond)
+               os=
+               basic_machine=$1
+               ;;
+       -scout)
+               ;;
+       -wrs)
+               os=-vxworks
+               basic_machine=$1
+               ;;
+       -chorusos*)
+               os=-chorusos
+               basic_machine=$1
+               ;;
+       -chorusrdb)
+               os=-chorusrdb
+               basic_machine=$1
+               ;;
+       -hiux*)
+               os=-hiuxwe2
+               ;;
+       -sco5)
+               os=-sco3.2v5
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco4)
+               os=-sco3.2v4
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco3.2.[4-9]*)
+               os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco3.2v[4-9]*)
+               # Don't forget version if it is 3.2v4 or newer.
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco*)
+               os=-sco3.2v2
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -udk*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -isc)
+               os=-isc2.2
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -clix*)
+               basic_machine=clipper-intergraph
+               ;;
+       -isc*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -lynx*)
+               os=-lynxos
+               ;;
+       -ptx*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+               ;;
+       -windowsnt*)
+               os=`echo $os | sed -e 's/windowsnt/winnt/'`
+               ;;
+       -psos*)
+               os=-psos
+               ;;
+       -mint | -mint[0-9]*)
+               basic_machine=m68k-atari
+               os=-mint
+               ;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+       # Recognize the basic CPU types without company name.
+       # Some are omitted here because they have special meanings below.
+       1750a | 580 \
+       | a29k \
+       | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+       | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+       | am33_2.0 \
+       | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \
+       | bfin \
+       | c4x | clipper \
+       | d10v | d30v | dlx | dsp16xx \
+       | fr30 | frv \
+       | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+       | i370 | i860 | i960 | ia64 \
+       | ip2k | iq2000 \
+       | m32r | m32rle | m68000 | m68k | m88k | maxq | mcore \
+       | mips | mipsbe | mipseb | mipsel | mipsle \
+       | mips16 \
+       | mips64 | mips64el \
+       | mips64vr | mips64vrel \
+       | mips64orion | mips64orionel \
+       | mips64vr4100 | mips64vr4100el \
+       | mips64vr4300 | mips64vr4300el \
+       | mips64vr5000 | mips64vr5000el \
+       | mipsisa32 | mipsisa32el \
+       | mipsisa32r2 | mipsisa32r2el \
+       | mipsisa64 | mipsisa64el \
+       | mipsisa64r2 | mipsisa64r2el \
+       | mipsisa64sb1 | mipsisa64sb1el \
+       | mipsisa64sr71k | mipsisa64sr71kel \
+       | mipstx39 | mipstx39el \
+       | mn10200 | mn10300 \
+       | msp430 \
+       | ns16k | ns32k \
+       | openrisc | or32 \
+       | pdp10 | pdp11 | pj | pjl \
+       | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \
+       | pyramid \
+       | sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \
+       | sh64 | sh64le \
+       | sparc | sparc64 | sparc64b | sparc86x | sparclet | sparclite \
+       | sparcv8 | sparcv9 | sparcv9b \
+       | strongarm \
+       | tahoe | thumb | tic4x | tic80 | tron \
+       | v850 | v850e \
+       | we32k \
+       | x86 | xscale | xscalee[bl] | xstormy16 | xtensa \
+       | z8k)
+               basic_machine=$basic_machine-unknown
+               ;;
+       m6811 | m68hc11 | m6812 | m68hc12)
+               # Motorola 68HC11/12.
+               basic_machine=$basic_machine-unknown
+               os=-none
+               ;;
+       m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+               ;;
+
+       # We use `pc' rather than `unknown'
+       # because (1) that's what they normally are, and
+       # (2) the word "unknown" tends to confuse beginning users.
+       i*86 | x86_64)
+         basic_machine=$basic_machine-pc
+         ;;
+       # Object if more than one company name word.
+       *-*-*)
+               echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+               exit 1
+               ;;
+       # Recognize the basic CPU types with company name.
+       580-* \
+       | a29k-* \
+       | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+       | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+       | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \
+       | arm-*  | armbe-* | armle-* | armeb-* | armv*-* \
+       | avr-* \
+       | bfin-* | bs2000-* \
+       | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \
+       | clipper-* | craynv-* | cydra-* \
+       | d10v-* | d30v-* | dlx-* \
+       | elxsi-* \
+       | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \
+       | h8300-* | h8500-* \
+       | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+       | i*86-* | i860-* | i960-* | ia64-* \
+       | ip2k-* | iq2000-* \
+       | m32r-* | m32rle-* \
+       | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+       | m88110-* | m88k-* | maxq-* | mcore-* \
+       | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+       | mips16-* \
+       | mips64-* | mips64el-* \
+       | mips64vr-* | mips64vrel-* \
+       | mips64orion-* | mips64orionel-* \
+       | mips64vr4100-* | mips64vr4100el-* \
+       | mips64vr4300-* | mips64vr4300el-* \
+       | mips64vr5000-* | mips64vr5000el-* \
+       | mipsisa32-* | mipsisa32el-* \
+       | mipsisa32r2-* | mipsisa32r2el-* \
+       | mipsisa64-* | mipsisa64el-* \
+       | mipsisa64r2-* | mipsisa64r2el-* \
+       | mipsisa64sb1-* | mipsisa64sb1el-* \
+       | mipsisa64sr71k-* | mipsisa64sr71kel-* \
+       | mipstx39-* | mipstx39el-* \
+       | mmix-* \
+       | msp430-* \
+       | none-* | np1-* | ns16k-* | ns32k-* \
+       | orion-* \
+       | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+       | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \
+       | pyramid-* \
+       | romp-* | rs6000-* \
+       | sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \
+       | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+       | sparc-* | sparc64-* | sparc64b-* | sparc86x-* | sparclet-* \
+       | sparclite-* \
+       | sparcv8-* | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \
+       | tahoe-* | thumb-* \
+       | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
+       | tron-* \
+       | v850-* | v850e-* | vax-* \
+       | we32k-* \
+       | x86-* | x86_64-* | xps100-* | xscale-* | xscalee[bl]-* \
+       | xstormy16-* | xtensa-* \
+       | ymp-* \
+       | z8k-*)
+               ;;
+       # Recognize the various machine names and aliases which stand
+       # for a CPU type and a company and sometimes even an OS.
+       386bsd)
+               basic_machine=i386-unknown
+               os=-bsd
+               ;;
+       3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+               basic_machine=m68000-att
+               ;;
+       3b*)
+               basic_machine=we32k-att
+               ;;
+       a29khif)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       abacus)
+               basic_machine=abacus-unknown
+               ;;
+       adobe68k)
+               basic_machine=m68010-adobe
+               os=-scout
+               ;;
+       alliant | fx80)
+               basic_machine=fx80-alliant
+               ;;
+       altos | altos3068)
+               basic_machine=m68k-altos
+               ;;
+       am29k)
+               basic_machine=a29k-none
+               os=-bsd
+               ;;
+       amd64)
+               basic_machine=x86_64-pc
+               ;;
+       amd64-*)
+               basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       amdahl)
+               basic_machine=580-amdahl
+               os=-sysv
+               ;;
+       amiga | amiga-*)
+               basic_machine=m68k-unknown
+               ;;
+       amigaos | amigados)
+               basic_machine=m68k-unknown
+               os=-amigaos
+               ;;
+       amigaunix | amix)
+               basic_machine=m68k-unknown
+               os=-sysv4
+               ;;
+       apollo68)
+               basic_machine=m68k-apollo
+               os=-sysv
+               ;;
+       apollo68bsd)
+               basic_machine=m68k-apollo
+               os=-bsd
+               ;;
+       aux)
+               basic_machine=m68k-apple
+               os=-aux
+               ;;
+       balance)
+               basic_machine=ns32k-sequent
+               os=-dynix
+               ;;
+       c90)
+               basic_machine=c90-cray
+               os=-unicos
+               ;;
+       convex-c1)
+               basic_machine=c1-convex
+               os=-bsd
+               ;;
+       convex-c2)
+               basic_machine=c2-convex
+               os=-bsd
+               ;;
+       convex-c32)
+               basic_machine=c32-convex
+               os=-bsd
+               ;;
+       convex-c34)
+               basic_machine=c34-convex
+               os=-bsd
+               ;;
+       convex-c38)
+               basic_machine=c38-convex
+               os=-bsd
+               ;;
+       cray | j90)
+               basic_machine=j90-cray
+               os=-unicos
+               ;;
+       craynv)
+               basic_machine=craynv-cray
+               os=-unicosmp
+               ;;
+       cr16c)
+               basic_machine=cr16c-unknown
+               os=-elf
+               ;;
+       crds | unos)
+               basic_machine=m68k-crds
+               ;;
+       crisv32 | crisv32-* | etraxfs*)
+               basic_machine=crisv32-axis
+               ;;
+       cris | cris-* | etrax*)
+               basic_machine=cris-axis
+               ;;
+       crx)
+               basic_machine=crx-unknown
+               os=-elf
+               ;;
+       da30 | da30-*)
+               basic_machine=m68k-da30
+               ;;
+       decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+               basic_machine=mips-dec
+               ;;
+       decsystem10* | dec10*)
+               basic_machine=pdp10-dec
+               os=-tops10
+               ;;
+       decsystem20* | dec20*)
+               basic_machine=pdp10-dec
+               os=-tops20
+               ;;
+       delta | 3300 | motorola-3300 | motorola-delta \
+             | 3300-motorola | delta-motorola)
+               basic_machine=m68k-motorola
+               ;;
+       delta88)
+               basic_machine=m88k-motorola
+               os=-sysv3
+               ;;
+       djgpp)
+               basic_machine=i586-pc
+               os=-msdosdjgpp
+               ;;
+       dpx20 | dpx20-*)
+               basic_machine=rs6000-bull
+               os=-bosx
+               ;;
+       dpx2* | dpx2*-bull)
+               basic_machine=m68k-bull
+               os=-sysv3
+               ;;
+       ebmon29k)
+               basic_machine=a29k-amd
+               os=-ebmon
+               ;;
+       elxsi)
+               basic_machine=elxsi-elxsi
+               os=-bsd
+               ;;
+       encore | umax | mmax)
+               basic_machine=ns32k-encore
+               ;;
+       es1800 | OSE68k | ose68k | ose | OSE)
+               basic_machine=m68k-ericsson
+               os=-ose
+               ;;
+       fx2800)
+               basic_machine=i860-alliant
+               ;;
+       genix)
+               basic_machine=ns32k-ns
+               ;;
+       gmicro)
+               basic_machine=tron-gmicro
+               os=-sysv
+               ;;
+       go32)
+               basic_machine=i386-pc
+               os=-go32
+               ;;
+       h3050r* | hiux*)
+               basic_machine=hppa1.1-hitachi
+               os=-hiuxwe2
+               ;;
+       h8300hms)
+               basic_machine=h8300-hitachi
+               os=-hms
+               ;;
+       h8300xray)
+               basic_machine=h8300-hitachi
+               os=-xray
+               ;;
+       h8500hms)
+               basic_machine=h8500-hitachi
+               os=-hms
+               ;;
+       harris)
+               basic_machine=m88k-harris
+               os=-sysv3
+               ;;
+       hp300-*)
+               basic_machine=m68k-hp
+               ;;
+       hp300bsd)
+               basic_machine=m68k-hp
+               os=-bsd
+               ;;
+       hp300hpux)
+               basic_machine=m68k-hp
+               os=-hpux
+               ;;
+       hp3k9[0-9][0-9] | hp9[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hp9k2[0-9][0-9] | hp9k31[0-9])
+               basic_machine=m68000-hp
+               ;;
+       hp9k3[2-9][0-9])
+               basic_machine=m68k-hp
+               ;;
+       hp9k6[0-9][0-9] | hp6[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hp9k7[0-79][0-9] | hp7[0-79][0-9])
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k78[0-9] | hp78[0-9])
+               # FIXME: really hppa2.0-hp
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+               # FIXME: really hppa2.0-hp
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[0-9][13679] | hp8[0-9][13679])
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[0-9][0-9] | hp8[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hppa-next)
+               os=-nextstep3
+               ;;
+       hppaosf)
+               basic_machine=hppa1.1-hp
+               os=-osf
+               ;;
+       hppro)
+               basic_machine=hppa1.1-hp
+               os=-proelf
+               ;;
+       i370-ibm* | ibm*)
+               basic_machine=i370-ibm
+               ;;
+# I'm not sure what "Sysv32" means.  Should this be sysv3.2?
+       i*86v32)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv32
+               ;;
+       i*86v4*)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv4
+               ;;
+       i*86v)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv
+               ;;
+       i*86sol2)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-solaris2
+               ;;
+       i386mach)
+               basic_machine=i386-mach
+               os=-mach
+               ;;
+       i386-vsta | vsta)
+               basic_machine=i386-unknown
+               os=-vsta
+               ;;
+       iris | iris4d)
+               basic_machine=mips-sgi
+               case $os in
+                   -irix*)
+                       ;;
+                   *)
+                       os=-irix4
+                       ;;
+               esac
+               ;;
+       isi68 | isi)
+               basic_machine=m68k-isi
+               os=-sysv
+               ;;
+       m88k-omron*)
+               basic_machine=m88k-omron
+               ;;
+       magnum | m3230)
+               basic_machine=mips-mips
+               os=-sysv
+               ;;
+       merlin)
+               basic_machine=ns32k-utek
+               os=-sysv
+               ;;
+       mingw32)
+               basic_machine=i386-pc
+               os=-mingw32
+               ;;
+       miniframe)
+               basic_machine=m68000-convergent
+               ;;
+       *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+               basic_machine=m68k-atari
+               os=-mint
+               ;;
+       mips3*-*)
+               basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+               ;;
+       mips3*)
+               basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+               ;;
+       monitor)
+               basic_machine=m68k-rom68k
+               os=-coff
+               ;;
+       morphos)
+               basic_machine=powerpc-unknown
+               os=-morphos
+               ;;
+       msdos)
+               basic_machine=i386-pc
+               os=-msdos
+               ;;
+       mvs)
+               basic_machine=i370-ibm
+               os=-mvs
+               ;;
+       ncr3000)
+               basic_machine=i486-ncr
+               os=-sysv4
+               ;;
+       netbsd386)
+               basic_machine=i386-unknown
+               os=-netbsd
+               ;;
+       netwinder)
+               basic_machine=armv4l-rebel
+               os=-linux
+               ;;
+       news | news700 | news800 | news900)
+               basic_machine=m68k-sony
+               os=-newsos
+               ;;
+       news1000)
+               basic_machine=m68030-sony
+               os=-newsos
+               ;;
+       news-3600 | risc-news)
+               basic_machine=mips-sony
+               os=-newsos
+               ;;
+       necv70)
+               basic_machine=v70-nec
+               os=-sysv
+               ;;
+       next | m*-next )
+               basic_machine=m68k-next
+               case $os in
+                   -nextstep* )
+                       ;;
+                   -ns2*)
+                     os=-nextstep2
+                       ;;
+                   *)
+                     os=-nextstep3
+                       ;;
+               esac
+               ;;
+       nh3000)
+               basic_machine=m68k-harris
+               os=-cxux
+               ;;
+       nh[45]000)
+               basic_machine=m88k-harris
+               os=-cxux
+               ;;
+       nindy960)
+               basic_machine=i960-intel
+               os=-nindy
+               ;;
+       mon960)
+               basic_machine=i960-intel
+               os=-mon960
+               ;;
+       nonstopux)
+               basic_machine=mips-compaq
+               os=-nonstopux
+               ;;
+       np1)
+               basic_machine=np1-gould
+               ;;
+       nsr-tandem)
+               basic_machine=nsr-tandem
+               ;;
+       op50n-* | op60c-*)
+               basic_machine=hppa1.1-oki
+               os=-proelf
+               ;;
+       or32 | or32-*)
+               basic_machine=or32-unknown
+               os=-coff
+               ;;
+       os400)
+               basic_machine=powerpc-ibm
+               os=-os400
+               ;;
+       OSE68000 | ose68000)
+               basic_machine=m68000-ericsson
+               os=-ose
+               ;;
+       os68k)
+               basic_machine=m68k-none
+               os=-os68k
+               ;;
+       pa-hitachi)
+               basic_machine=hppa1.1-hitachi
+               os=-hiuxwe2
+               ;;
+       paragon)
+               basic_machine=i860-intel
+               os=-osf
+               ;;
+       pbd)
+               basic_machine=sparc-tti
+               ;;
+       pbb)
+               basic_machine=m68k-tti
+               ;;
+       pc532 | pc532-*)
+               basic_machine=ns32k-pc532
+               ;;
+       pentium | p5 | k5 | k6 | nexgen | viac3)
+               basic_machine=i586-pc
+               ;;
+       pentiumpro | p6 | 6x86 | athlon | athlon_*)
+               basic_machine=i686-pc
+               ;;
+       pentiumii | pentium2 | pentiumiii | pentium3)
+               basic_machine=i686-pc
+               ;;
+       pentium4)
+               basic_machine=i786-pc
+               ;;
+       pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+               basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentiumpro-* | p6-* | 6x86-* | athlon-*)
+               basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+               basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentium4-*)
+               basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pn)
+               basic_machine=pn-gould
+               ;;
+       power)  basic_machine=power-ibm
+               ;;
+       ppc)    basic_machine=powerpc-unknown
+               ;;
+       ppc-*)  basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ppcle | powerpclittle | ppc-le | powerpc-little)
+               basic_machine=powerpcle-unknown
+               ;;
+       ppcle-* | powerpclittle-*)
+               basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ppc64)  basic_machine=powerpc64-unknown
+               ;;
+       ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+               basic_machine=powerpc64le-unknown
+               ;;
+       ppc64le-* | powerpc64little-*)
+               basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ps2)
+               basic_machine=i386-ibm
+               ;;
+       pw32)
+               basic_machine=i586-unknown
+               os=-pw32
+               ;;
+       rom68k)
+               basic_machine=m68k-rom68k
+               os=-coff
+               ;;
+       rm[46]00)
+               basic_machine=mips-siemens
+               ;;
+       rtpc | rtpc-*)
+               basic_machine=romp-ibm
+               ;;
+       s390 | s390-*)
+               basic_machine=s390-ibm
+               ;;
+       s390x | s390x-*)
+               basic_machine=s390x-ibm
+               ;;
+       sa29200)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       sb1)
+               basic_machine=mipsisa64sb1-unknown
+               ;;
+       sb1el)
+               basic_machine=mipsisa64sb1el-unknown
+               ;;
+       sei)
+               basic_machine=mips-sei
+               os=-seiux
+               ;;
+       sequent)
+               basic_machine=i386-sequent
+               ;;
+       sh)
+               basic_machine=sh-hitachi
+               os=-hms
+               ;;
+       sh64)
+               basic_machine=sh64-unknown
+               ;;
+       sparclite-wrs | simso-wrs)
+               basic_machine=sparclite-wrs
+               os=-vxworks
+               ;;
+       sps7)
+               basic_machine=m68k-bull
+               os=-sysv2
+               ;;
+       spur)
+               basic_machine=spur-unknown
+               ;;
+       st2000)
+               basic_machine=m68k-tandem
+               ;;
+       stratus)
+               basic_machine=i860-stratus
+               os=-sysv4
+               ;;
+       sun2)
+               basic_machine=m68000-sun
+               ;;
+       sun2os3)
+               basic_machine=m68000-sun
+               os=-sunos3
+               ;;
+       sun2os4)
+               basic_machine=m68000-sun
+               os=-sunos4
+               ;;
+       sun3os3)
+               basic_machine=m68k-sun
+               os=-sunos3
+               ;;
+       sun3os4)
+               basic_machine=m68k-sun
+               os=-sunos4
+               ;;
+       sun4os3)
+               basic_machine=sparc-sun
+               os=-sunos3
+               ;;
+       sun4os4)
+               basic_machine=sparc-sun
+               os=-sunos4
+               ;;
+       sun4sol2)
+               basic_machine=sparc-sun
+               os=-solaris2
+               ;;
+       sun3 | sun3-*)
+               basic_machine=m68k-sun
+               ;;
+       sun4)
+               basic_machine=sparc-sun
+               ;;
+       sun386 | sun386i | roadrunner)
+               basic_machine=i386-sun
+               ;;
+       sv1)
+               basic_machine=sv1-cray
+               os=-unicos
+               ;;
+       symmetry)
+               basic_machine=i386-sequent
+               os=-dynix
+               ;;
+       t3e)
+               basic_machine=alphaev5-cray
+               os=-unicos
+               ;;
+       t90)
+               basic_machine=t90-cray
+               os=-unicos
+               ;;
+       tic54x | c54x*)
+               basic_machine=tic54x-unknown
+               os=-coff
+               ;;
+       tic55x | c55x*)
+               basic_machine=tic55x-unknown
+               os=-coff
+               ;;
+       tic6x | c6x*)
+               basic_machine=tic6x-unknown
+               os=-coff
+               ;;
+       tx39)
+               basic_machine=mipstx39-unknown
+               ;;
+       tx39el)
+               basic_machine=mipstx39el-unknown
+               ;;
+       toad1)
+               basic_machine=pdp10-xkl
+               os=-tops20
+               ;;
+       tower | tower-32)
+               basic_machine=m68k-ncr
+               ;;
+       tpf)
+               basic_machine=s390x-ibm
+               os=-tpf
+               ;;
+       udi29k)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       ultra3)
+               basic_machine=a29k-nyu
+               os=-sym1
+               ;;
+       v810 | necv810)
+               basic_machine=v810-nec
+               os=-none
+               ;;
+       vaxv)
+               basic_machine=vax-dec
+               os=-sysv
+               ;;
+       vms)
+               basic_machine=vax-dec
+               os=-vms
+               ;;
+       vpp*|vx|vx-*)
+               basic_machine=f301-fujitsu
+               ;;
+       vxworks960)
+               basic_machine=i960-wrs
+               os=-vxworks
+               ;;
+       vxworks68)
+               basic_machine=m68k-wrs
+               os=-vxworks
+               ;;
+       vxworks29k)
+               basic_machine=a29k-wrs
+               os=-vxworks
+               ;;
+       w65*)
+               basic_machine=w65-wdc
+               os=-none
+               ;;
+       w89k-*)
+               basic_machine=hppa1.1-winbond
+               os=-proelf
+               ;;
+       xbox)
+               basic_machine=i686-pc
+               os=-mingw32
+               ;;
+       xps | xps100)
+               basic_machine=xps100-honeywell
+               ;;
+       ymp)
+               basic_machine=ymp-cray
+               os=-unicos
+               ;;
+       z8k-*-coff)
+               basic_machine=z8k-unknown
+               os=-sim
+               ;;
+       none)
+               basic_machine=none-none
+               os=-none
+               ;;
+
+# Here we handle the default manufacturer of certain CPU types.  It is in
+# some cases the only manufacturer, in others, it is the most popular.
+       w89k)
+               basic_machine=hppa1.1-winbond
+               ;;
+       op50n)
+               basic_machine=hppa1.1-oki
+               ;;
+       op60c)
+               basic_machine=hppa1.1-oki
+               ;;
+       romp)
+               basic_machine=romp-ibm
+               ;;
+       mmix)
+               basic_machine=mmix-knuth
+               ;;
+       rs6000)
+               basic_machine=rs6000-ibm
+               ;;
+       vax)
+               basic_machine=vax-dec
+               ;;
+       pdp10)
+               # there are many clones, so DEC is not a safe bet
+               basic_machine=pdp10-unknown
+               ;;
+       pdp11)
+               basic_machine=pdp11-dec
+               ;;
+       we32k)
+               basic_machine=we32k-att
+               ;;
+       sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele)
+               basic_machine=sh-unknown
+               ;;
+       sh64)
+               basic_machine=sh64-unknown
+               ;;
+       sparc | sparcv8 | sparcv9 | sparcv9b)
+               basic_machine=sparc-sun
+               ;;
+       cydra)
+               basic_machine=cydra-cydrome
+               ;;
+       orion)
+               basic_machine=orion-highlevel
+               ;;
+       orion105)
+               basic_machine=clipper-highlevel
+               ;;
+       mac | mpw | mac-mpw)
+               basic_machine=m68k-apple
+               ;;
+       pmac | pmac-mpw)
+               basic_machine=powerpc-apple
+               ;;
+       *-unknown)
+               # Make sure to match an already-canonicalized machine name.
+               ;;
+       *)
+               echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+               exit 1
+               ;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+       *-digital*)
+               basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+               ;;
+       *-commodore*)
+               basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+               ;;
+       *)
+               ;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+        # First match some system type aliases
+        # that might get confused with valid system types.
+       # -solaris* is a basic system type, with this one exception.
+       -solaris1 | -solaris1.*)
+               os=`echo $os | sed -e 's|solaris1|sunos4|'`
+               ;;
+       -solaris)
+               os=-solaris2
+               ;;
+       -svr4*)
+               os=-sysv4
+               ;;
+       -unixware*)
+               os=-sysv4.2uw
+               ;;
+       -gnu/linux*)
+               os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+               ;;
+       # First accept the basic system types.
+       # The portable systems comes first.
+       # Each alternative MUST END IN A *, to match a version number.
+       # -sysv* is not here because it comes later, after sysvr4.
+       -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+             | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\
+             | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \
+             | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+             | -aos* \
+             | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+             | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+             | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \
+             | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
+             | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+             | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+             | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+             | -chorusos* | -chorusrdb* \
+             | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+             | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \
+             | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+             | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+             | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+             | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+             | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+             | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly*)
+       # Remember, each alternative MUST END IN *, to match a version number.
+               ;;
+       -qnx*)
+               case $basic_machine in
+                   x86-* | i*86-*)
+                       ;;
+                   *)
+                       os=-nto$os
+                       ;;
+               esac
+               ;;
+       -nto-qnx*)
+               ;;
+       -nto*)
+               os=`echo $os | sed -e 's|nto|nto-qnx|'`
+               ;;
+       -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+             | -windows* | -osx | -abug | -netware* | -os9* | -beos* \
+             | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+               ;;
+       -mac*)
+               os=`echo $os | sed -e 's|mac|macos|'`
+               ;;
+       -linux-dietlibc)
+               os=-linux-dietlibc
+               ;;
+       -linux*)
+               os=`echo $os | sed -e 's|linux|linux-gnu|'`
+               ;;
+       -sunos5*)
+               os=`echo $os | sed -e 's|sunos5|solaris2|'`
+               ;;
+       -sunos6*)
+               os=`echo $os | sed -e 's|sunos6|solaris3|'`
+               ;;
+       -opened*)
+               os=-openedition
+               ;;
+        -os400*)
+               os=-os400
+               ;;
+       -wince*)
+               os=-wince
+               ;;
+       -osfrose*)
+               os=-osfrose
+               ;;
+       -osf*)
+               os=-osf
+               ;;
+       -utek*)
+               os=-bsd
+               ;;
+       -dynix*)
+               os=-bsd
+               ;;
+       -acis*)
+               os=-aos
+               ;;
+       -atheos*)
+               os=-atheos
+               ;;
+       -syllable*)
+               os=-syllable
+               ;;
+       -386bsd)
+               os=-bsd
+               ;;
+       -ctix* | -uts*)
+               os=-sysv
+               ;;
+       -nova*)
+               os=-rtmk-nova
+               ;;
+       -ns2 )
+               os=-nextstep2
+               ;;
+       -nsk*)
+               os=-nsk
+               ;;
+       # Preserve the version number of sinix5.
+       -sinix5.*)
+               os=`echo $os | sed -e 's|sinix|sysv|'`
+               ;;
+       -sinix*)
+               os=-sysv4
+               ;;
+        -tpf*)
+               os=-tpf
+               ;;
+       -triton*)
+               os=-sysv3
+               ;;
+       -oss*)
+               os=-sysv3
+               ;;
+       -svr4)
+               os=-sysv4
+               ;;
+       -svr3)
+               os=-sysv3
+               ;;
+       -sysvr4)
+               os=-sysv4
+               ;;
+       # This must come after -sysvr4.
+       -sysv*)
+               ;;
+       -ose*)
+               os=-ose
+               ;;
+       -es1800*)
+               os=-ose
+               ;;
+       -xenix)
+               os=-xenix
+               ;;
+       -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+               os=-mint
+               ;;
+       -aros*)
+               os=-aros
+               ;;
+       -kaos*)
+               os=-kaos
+               ;;
+       -zvmoe)
+               os=-zvmoe
+               ;;
+       -none)
+               ;;
+       *)
+               # Get rid of the `-' at the beginning of $os.
+               os=`echo $os | sed 's/[^-]*-//'`
+               echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+               exit 1
+               ;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system.  Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+       *-acorn)
+               os=-riscix1.2
+               ;;
+       arm*-rebel)
+               os=-linux
+               ;;
+       arm*-semi)
+               os=-aout
+               ;;
+    c4x-* | tic4x-*)
+        os=-coff
+        ;;
+       # This must come before the *-dec entry.
+       pdp10-*)
+               os=-tops20
+               ;;
+       pdp11-*)
+               os=-none
+               ;;
+       *-dec | vax-*)
+               os=-ultrix4.2
+               ;;
+       m68*-apollo)
+               os=-domain
+               ;;
+       i386-sun)
+               os=-sunos4.0.2
+               ;;
+       m68000-sun)
+               os=-sunos3
+               # This also exists in the configure program, but was not the
+               # default.
+               # os=-sunos4
+               ;;
+       m68*-cisco)
+               os=-aout
+               ;;
+       mips*-cisco)
+               os=-elf
+               ;;
+       mips*-*)
+               os=-elf
+               ;;
+       or32-*)
+               os=-coff
+               ;;
+       *-tti)  # must be before sparc entry or we get the wrong os.
+               os=-sysv3
+               ;;
+       sparc-* | *-sun)
+               os=-sunos4.1.1
+               ;;
+       *-be)
+               os=-beos
+               ;;
+       *-ibm)
+               os=-aix
+               ;;
+       *-knuth)
+               os=-mmixware
+               ;;
+       *-wec)
+               os=-proelf
+               ;;
+       *-winbond)
+               os=-proelf
+               ;;
+       *-oki)
+               os=-proelf
+               ;;
+       *-hp)
+               os=-hpux
+               ;;
+       *-hitachi)
+               os=-hiux
+               ;;
+       i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+               os=-sysv
+               ;;
+       *-cbm)
+               os=-amigaos
+               ;;
+       *-dg)
+               os=-dgux
+               ;;
+       *-dolphin)
+               os=-sysv3
+               ;;
+       m68k-ccur)
+               os=-rtu
+               ;;
+       m88k-omron*)
+               os=-luna
+               ;;
+       *-next )
+               os=-nextstep
+               ;;
+       *-sequent)
+               os=-ptx
+               ;;
+       *-crds)
+               os=-unos
+               ;;
+       *-ns)
+               os=-genix
+               ;;
+       i370-*)
+               os=-mvs
+               ;;
+       *-next)
+               os=-nextstep3
+               ;;
+       *-gould)
+               os=-sysv
+               ;;
+       *-highlevel)
+               os=-bsd
+               ;;
+       *-encore)
+               os=-bsd
+               ;;
+       *-sgi)
+               os=-irix
+               ;;
+       *-siemens)
+               os=-sysv4
+               ;;
+       *-masscomp)
+               os=-rtu
+               ;;
+       f30[01]-fujitsu | f700-fujitsu)
+               os=-uxpv
+               ;;
+       *-rom68k)
+               os=-coff
+               ;;
+       *-*bug)
+               os=-coff
+               ;;
+       *-apple)
+               os=-macos
+               ;;
+       *-atari*)
+               os=-mint
+               ;;
+       *)
+               os=-none
+               ;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer.  We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+       *-unknown)
+               case $os in
+                       -riscix*)
+                               vendor=acorn
+                               ;;
+                       -sunos*)
+                               vendor=sun
+                               ;;
+                       -aix*)
+                               vendor=ibm
+                               ;;
+                       -beos*)
+                               vendor=be
+                               ;;
+                       -hpux*)
+                               vendor=hp
+                               ;;
+                       -mpeix*)
+                               vendor=hp
+                               ;;
+                       -hiux*)
+                               vendor=hitachi
+                               ;;
+                       -unos*)
+                               vendor=crds
+                               ;;
+                       -dgux*)
+                               vendor=dg
+                               ;;
+                       -luna*)
+                               vendor=omron
+                               ;;
+                       -genix*)
+                               vendor=ns
+                               ;;
+                       -mvs* | -opened*)
+                               vendor=ibm
+                               ;;
+                       -os400*)
+                               vendor=ibm
+                               ;;
+                       -ptx*)
+                               vendor=sequent
+                               ;;
+                       -tpf*)
+                               vendor=ibm
+                               ;;
+                       -vxsim* | -vxworks* | -windiss*)
+                               vendor=wrs
+                               ;;
+                       -aux*)
+                               vendor=apple
+                               ;;
+                       -hms*)
+                               vendor=hitachi
+                               ;;
+                       -mpw* | -macos*)
+                               vendor=apple
+                               ;;
+                       -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+                               vendor=atari
+                               ;;
+                       -vos*)
+                               vendor=stratus
+                               ;;
+               esac
+               basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+               ;;
+esac
+
+echo $basic_machine$os
+exit 0
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/tools/dsync-0.0/buildlib/configure.mak b/tools/dsync-0.0/buildlib/configure.mak
new file mode 100644 (file)
index 0000000..2124ef8
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- make -*-
+
+# This make fragment is included by the toplevel make to handle configure
+# and setup. It defines a target called startup that when run will init
+# the build directory, generate configure from configure.in, create aclocal
+# and has rules to run config.status should one of the .in files change.
+
+# Input
+#  BUILDDIR - The build directory
+#  CONVERTED - List of files output by configure $(BUILD) is prepended
+#              The caller must provide depends for these files
+# It would be a fairly good idea to run this after a cvs checkout.
+BUILDDIR=build
+
+.PHONY: startup
+startup: configure $(BUILDDIR)/config.status $(addprefix $(BUILDDIR)/,$(CONVERTED)) 
+
+configure: aclocal.m4 configure.in
+       autoconf
+
+aclocal.m4:
+       aclocal -I buildlib
+$(BUILDDIR)/config.status: configure
+       test -e $(BUILDDIR) || mkdir $(BUILDDIR)        
+       (HERE=`pwd`; cd $(BUILDDIR) && $$HERE/configure)
+$(addprefix $(BUILDDIR)/,$(CONVERTED)):
+       (cd $(BUILDDIR) && ./config.status)
diff --git a/tools/dsync-0.0/buildlib/copy.mak b/tools/dsync-0.0/buildlib/copy.mak
new file mode 100644 (file)
index 0000000..973c485
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- make -*-
+
+# This installs arbitary files into a directory
+
+# Input
+# $(SOURCE) - The documents to use
+# $(TO)     - The directory to put them in
+# All output is writtin to files in the build/$(TO) directory
+
+# See defaults.mak for information about LOCAL
+
+# Some local definitions
+LOCAL := copy-$(firstword $(SOURCE))
+$(LOCAL)-LIST := $(addprefix $(TO)/,$(SOURCE))
+
+# Install generation hooks
+doc: $($(LOCAL)-LIST)
+veryclean: veryclean/$(LOCAL)
+
+$($(LOCAL)-LIST) : $(TO)/% : %
+       echo Installing $< to $(@D)
+       cp $< $(@D)
+
+# Clean rule
+.PHONY: veryclean/$(LOCAL)
+veryclean/$(LOCAL):
+       -rm -rf $($(@F)-LIST)
diff --git a/tools/dsync-0.0/buildlib/debiandoc.mak b/tools/dsync-0.0/buildlib/debiandoc.mak
new file mode 100644 (file)
index 0000000..5e08bda
--- /dev/null
@@ -0,0 +1,58 @@
+# -*- make -*-
+
+# This processes debian-doc sgml to produce html and plain text output
+
+# Input
+# $(SOURCE) - The documents to use
+
+# All output is writtin to files in the build doc directory
+
+# See defaults.mak for information about LOCAL
+
+# Some local definitions
+LOCAL := debiandoc-$(firstword $(SOURCE))
+$(LOCAL)-HTML := $(addsuffix .html,$(addprefix $(DOC)/,$(basename $(SOURCE))))
+$(LOCAL)-TEXT := $(addsuffix .text,$(addprefix $(DOC)/,$(basename $(SOURCE))))
+
+#---------
+
+# Rules to build HTML documentations
+ifdef DEBIANDOC_HTML
+
+# Install generation hooks
+doc: $($(LOCAL)-HTML)
+veryclean: veryclean/html/$(LOCAL)
+
+vpath %.sgml $(SUBDIRS)
+$(DOC)/%.html: %.sgml
+       echo Creating html for $< to $@
+       -rm -rf $@
+       (HERE=`pwd`; cd $(@D) && debiandoc2html $$HERE/$<)
+
+# Clean rule
+.PHONY: veryclean/html/$(LOCAL)
+veryclean/html/$(LOCAL):
+       -rm -rf $($(@F)-HTML)
+       
+endif
+
+#---------
+
+# Rules to build Text documentations
+ifdef DEBIANDOC_TEXT
+
+# Install generation hooks
+doc: $($(LOCAL)-TEXT)
+veryclean: veryclean/text/$(LOCAL)
+
+vpath %.sgml $(SUBDIRS)
+$(DOC)/%.text: %.sgml
+       echo Creating text for $< to $@
+       debiandoc2text -O $< > $@
+
+# Clean rule
+.PHONY: veryclean/text/$(LOCAL)
+veryclean/text/$(LOCAL):
+       -rm -rf $($(@F)-TEXT)
+       
+endif
diff --git a/tools/dsync-0.0/buildlib/defaults.mak b/tools/dsync-0.0/buildlib/defaults.mak
new file mode 100644 (file)
index 0000000..d04b67f
--- /dev/null
@@ -0,0 +1,136 @@
+# -*- make -*-
+
+# This file configures the default environment for the make system
+# The way it works is fairly simple, each module is defined in it's
+# own *.mak file. It expects a set of variables to be set to values
+# for it to operate as expected. When included the module generates
+# the requested rules based on the contents of its control variables.
+
+# This works out very well and allows a good degree of flexability.
+# To accomidate some of the features we introduce the concept of 
+# local variables. To do this we use the 'Computed Names' feature of
+# gmake. Each module declares a LOCAL scope and access it with,
+#   $($(LOCAL)-VAR)
+# This works very well but it is important to rembember that within
+# a rule the LOCAL var is unavailble, it will have to be constructed
+# from the information in the rule invokation. For stock rules like 
+# clean this is simple, we use a local clean rule called clean/$(LOCAL)
+# and then within the rule $(@F) gets back $(LOCAL)! Other rules will
+# have to use some other mechanism (filter perhaps?) The reason such
+# lengths are used is so that each directory can contain several 'instances'
+# of any given module. I notice that the very latest gmake has the concept
+# of local variables for rules. It is possible this feature in conjunction
+# with the generated names will provide a very powerfull solution indeed!
+
+# A build directory is used by default, all generated items get put into
+# there. However unlike automake this is not done with a VPATH build
+# (vpath builds break the distinction between #include "" and #include <>)
+# but by explicly setting the BUILD variable. Make is invoked from
+# within the source itself which is much more compatible with compilation
+# environments.
+ifndef NOISY
+.SILENT:
+endif
+
+# Search for the build directory
+ifdef BUILD
+BUILD_POSSIBLE := $(BUILD) $(BASE)/$(BUILD)
+else
+BUILD_POSSIBLE := $(BASE) $(BASE)/build-$(shell uname -m) $(BASE)/build
+endif
+
+BUILDX:= $(foreach i,$(BUILD_POSSIBLE),$(wildcard $(i)/environment.mak*))
+BUILDX:= $(patsubst %/,%,$(firstword $(dir $(BUILDX))))
+
+ifeq ($(words $(BUILDX)),0)
+error-all:
+       echo Can't find the build directory in $(BUILD_POSSIBLE) -- use BUILD=
+endif
+
+override BUILD := $(BUILDX)
+
+# Base definitions
+INCLUDE := $(BUILD)/include
+BIN := $(BUILD)/bin
+LIB := $(BIN)
+OBJ := $(BUILD)/obj/$(SUBDIR)
+DEP := $(OBJ)
+DOC := $(BUILD)/docs
+
+# Module types
+LIBRARY_H = $(BASE)/buildlib/library.mak
+DEBIANDOC_H = $(BASE)/buildlib/debiandoc.mak
+MANPAGE_H = $(BASE)/buildlib/manpage.mak
+PROGRAM_H = $(BASE)/buildlib/program.mak
+COPY_H = $(BASE)/buildlib/copy.mak
+YODL_MANPAGE_H = $(BASE)/buildlib/yodl_manpage.mak
+
+ifdef STATICLIBS
+LIBRARY_H += $(BASE)/buildlib/staticlibrary.mak
+endif
+
+ifdef ONLYSTATICLIBS
+LIBRARY_H = $(BASE)/buildlib/staticlibrary.mak
+endif
+
+# Source location control
+# SUBDIRS specifies sub components of the module that
+# may be located in subdrictories of the source dir. 
+# This should be declared before including this file
+SUBDIRS+=
+
+# Header file control. 
+# TARGETDIRS indicitates all of the locations that public headers 
+# will be published to.
+# This should be declared before including this file
+HEADER_TARGETDIRS+=
+
+# Options
+include $(BUILD)/environment.mak
+CPPFLAGS+= -I$(INCLUDE)
+LDFLAGS+= -L$(LIB)
+
+# Phony rules. Other things hook these by appending to the dependency
+# list
+.PHONY: headers library clean veryclean all binary program doc
+.PHONY: maintainer-clean dist-clean distclean pristine sanity
+all: binary doc
+binary: library program
+maintainer-clean dist-clean distclean pristine sanity: veryclean
+headers library clean veryclean program:
+
+veryclean:
+       echo Very Clean done for $(SUBDIR)
+clean:
+       echo Clean done for $(SUBDIR)
+       
+# Header file control. We want all published interface headers to go
+# into the build directory from thier source dirs. We setup some
+# search paths here
+vpath %.h $(SUBDIRS)
+$(INCLUDE)/%.h $(addprefix $(INCLUDE)/,$(addsuffix /%.h,$(HEADER_TARGETDIRS))) : %.h
+       cp $< $@
+
+# Dependency generation. We want to generate a .d file using gnu cpp.
+# For GNU systems the compiler can spit out a .d file while it is compiling,
+# this is specified with the INLINEDEPFLAG. Other systems might have a 
+# makedep program that can be called after compiling, that's illistrated
+# by the DEPFLAG case.
+# Compile rules are expected to call this macro after calling the compiler
+ifdef INLINEDEPFLAG
+ define DoDep
+       sed -e "1s/.*:/$(subst /,\\/,$@):/" $(basename $(@F)).d > $(DEP)/$(@F).d
+       -rm -f $(basename $(@F)).d
+ endef
+else
+ ifdef DEPFLAG
+  define DoDep
+       $(CXX) $(DEPFLAG) $(CPPFLAGS) -o $@ $<
+       sed -e "1s/.*:/$(subst /,\\/,$@):/" $(basename $(@F)).d > $(DEP)/$(@F).d
+       -rm -f $(basename $(@F)).d
+  endef
+ else
+  define DoDep
+  endef
+ endif
+endif  
diff --git a/tools/dsync-0.0/buildlib/environment.mak.in b/tools/dsync-0.0/buildlib/environment.mak.in
new file mode 100644 (file)
index 0000000..ab9938d
--- /dev/null
@@ -0,0 +1,35 @@
+# This file contains everything that autoconf guessed for your system.
+# if you want you can edit it, just don't re-run configure.
+
+# C++ compiler options
+AR = @AR@
+CC = @CC@
+CPPFLAGS+= @CPPFLAGS@ @DEFS@ -D_REENTRANT
+CXX = @CXX@
+CXXFLAGS+= @CXXFLAGS@
+
+# Linker stuff
+PICFLAGS+= -fPIC -DPIC
+LFLAGS+= @LDFLAGS@
+
+# Dep generation - this only works for gnu stuff
+INLINEDEPFLAG =
+
+# Debian doc stuff
+DEBIANDOC_HTML = @DEBIANDOC_HTML@
+DEBIANDOC_TEXT = @DEBIANDOC_TEXT@
+
+# YODL for the man pages
+YODL_MAN = @YODL_MAN@
+
+# Various library checks
+PTHREADLIB = @PTHREADLIB@
+HAVE_C9X = @HAVE_C9X@
+
+# Shared library things
+HOST_OS = @host_os@
+ifeq ($(HOST_OS),linux-gnu)
+   ONLYSHAREDLIBS = yes
+   SONAME_MAGIC=-Wl,-h -Wl,
+   LFLAGS_SO=
+endif
diff --git a/tools/dsync-0.0/buildlib/install-sh b/tools/dsync-0.0/buildlib/install-sh
new file mode 100644 (file)
index 0000000..ebc6691
--- /dev/null
@@ -0,0 +1,250 @@
+#! /bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission.  M.I.T. makes no representations about the
+# suitability of this software for any purpose.  It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+       -c) instcmd="$cpprog"
+           shift
+           continue;;
+
+       -d) dir_arg=true
+           shift
+           continue;;
+
+       -m) chmodcmd="$chmodprog $2"
+           shift
+           shift
+           continue;;
+
+       -o) chowncmd="$chownprog $2"
+           shift
+           shift
+           continue;;
+
+       -g) chgrpcmd="$chgrpprog $2"
+           shift
+           shift
+           continue;;
+
+       -s) stripcmd="$stripprog"
+           shift
+           continue;;
+
+       -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+           shift
+           continue;;
+
+       -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+           shift
+           continue;;
+
+       *)  if [ x"$src" = x ]
+           then
+               src=$1
+           else
+               # this colon is to work around a 386BSD /bin/sh bug
+               :
+               dst=$1
+           fi
+           shift
+           continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+       echo "install:  no input file specified"
+       exit 1
+else
+       true
+fi
+
+if [ x"$dir_arg" != x ]; then
+       dst=$src
+       src=""
+       
+       if [ -d $dst ]; then
+               instcmd=:
+       else
+               instcmd=mkdir
+       fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+       if [ -f $src -o -d $src ]
+       then
+               true
+       else
+               echo "install:  $src does not exist"
+               exit 1
+       fi
+       
+       if [ x"$dst" = x ]
+       then
+               echo "install:  no destination specified"
+               exit 1
+       else
+               true
+       fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+       if [ -d $dst ]
+       then
+               dst="$dst"/`basename $src`
+       else
+               true
+       fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='   
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+       pathcomp="${pathcomp}${1}"
+       shift
+
+       if [ ! -d "${pathcomp}" ] ;
+        then
+               $mkdirprog "${pathcomp}"
+       else
+               true
+       fi
+
+       pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+       $doit $instcmd $dst &&
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+       if [ x"$transformarg" = x ] 
+       then
+               dstfile=`basename $dst`
+       else
+               dstfile=`basename $dst $transformbasename | 
+                       sed $transformarg`$transformbasename
+       fi
+
+# don't allow the sed command to completely eliminate the filename
+
+       if [ x"$dstfile" = x ] 
+       then
+               dstfile=`basename $dst`
+       else
+               true
+       fi
+
+# Make a temp file name in the proper directory.
+
+       dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+       $doit $instcmd $src $dsttmp &&
+
+       trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+       $doit $rmcmd -f $dstdir/$dstfile &&
+       $doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
diff --git a/tools/dsync-0.0/buildlib/inttypes.h.in b/tools/dsync-0.0/buildlib/inttypes.h.in
new file mode 100644 (file)
index 0000000..3be7207
--- /dev/null
@@ -0,0 +1,43 @@
+/* This is an ISO C 9X header file. We omit this copy to the include 
+   directory if the local platform does not have inttypes.h, it contains
+   [u]int[8,16,32]_t fixed width types */
+
+#include <config.h>
+
+/* Generate the fixed bit size types */
+#if SIZEOF_INT == 4
+  typedef int int32_t;
+  typedef unsigned int uint32_t;
+#else
+# if SIZEOF_LONG == 4
+  typedef long int32_t;
+  typedef unsigned long uint32_t;
+# else
+#  if SIZEOF_SHORT == 4
+    typedef short int32_t;
+    typedef unsigned short uint32_t;
+#  else
+#   error Must have a form of 32-bit integer
+#  endif
+# endif
+#endif
+
+#if SIZEOF_INT == 2
+  typedef int int16_t;
+  typedef unsigned int uint16_t;
+#else
+# if SIZEOF_LONG == 2
+   typedef long int16_t;
+   typedef unsigned long uint16_t;
+# else
+#  if SIZEOF_SHORT == 2
+    typedef short int16_t;
+    typedef unsigned short uint16_t;
+#  else
+#   error Must have a form of 16-bit integer
+#  endif
+# endif
+#endif
+
+typedef signed char int8_t;
+typedef unsigned char uint8_t;
diff --git a/tools/dsync-0.0/buildlib/library.mak b/tools/dsync-0.0/buildlib/library.mak
new file mode 100644 (file)
index 0000000..565baa3
--- /dev/null
@@ -0,0 +1,65 @@
+# -*- make -*-
+
+# This creates a shared library.
+
+# Input
+# $(SOURCE) - The source code to use
+# $(HEADERS) - Exported header files and private header files
+# $(LIBRARY) - The name of the library without lib or .so 
+# $(MAJOR) - The major version number of this library
+# $(MINOR) - The minor version number of this library
+
+# All output is writtin to .opic files in the build directory to
+# signify the PIC output.
+
+# See defaults.mak for information about LOCAL
+
+# Some local definitions
+LOCAL := lib$(LIBRARY).so.$(MAJOR).$(MINOR)
+$(LOCAL)-OBJS := $(addprefix $(OBJ)/,$(addsuffix .opic,$(notdir $(basename $(SOURCE)))))
+$(LOCAL)-DEP := $(addprefix $(DEP)/,$(addsuffix .opic.d,$(notdir $(basename $(SOURCE)))))
+$(LOCAL)-HEADERS := $(addprefix $(INCLUDE)/,$(HEADERS))
+$(LOCAL)-SONAME := lib$(LIBRARY).so.$(MAJOR)
+$(LOCAL)-SLIBS := $(SLIBS)
+$(LOCAL)-LIBRARY := $(LIBRARY)
+
+# Install the command hooks
+headers: $($(LOCAL)-HEADERS)
+library: $(LIB)/lib$(LIBRARY).so $(LIB)/lib$(LIBRARY).so.$(MAJOR)
+clean: clean/$(LOCAL)
+veryclean: veryclean/$(LOCAL)
+
+# The clean rules
+.PHONY: clean/$(LOCAL) veryclean/$(LOCAL)
+clean/$(LOCAL):
+       -rm -f $($(@F)-OBJS) $($(@F)-DEP)
+veryclean/$(LOCAL): clean/$(LOCAL)
+       -rm -f $($(@F)-HEADERS) $(LIB)/lib$($(@F)-LIBRARY).so*
+
+# Build rules for the two symlinks
+.PHONY: $(LIB)/lib$(LIBRARY).so.$(MAJOR) $(LIB)/lib$(LIBRARY).so
+$(LIB)/lib$(LIBRARY).so.$(MAJOR): $(LIB)/lib$(LIBRARY).so.$(MAJOR).$(MINOR)
+       ln -sf $(<F) $@
+$(LIB)/lib$(LIBRARY).so: $(LIB)/lib$(LIBRARY).so.$(MAJOR).$(MINOR)
+       ln -sf $(<F) $@
+       
+# The binary build rule
+$(LIB)/lib$(LIBRARY).so.$(MAJOR).$(MINOR): $($(LOCAL)-HEADERS) $($(LOCAL)-OBJS)
+       -rm -f $(LIB)/lib$($(@F)-LIBRARY).so* 2> /dev/null
+       echo Building shared library $@
+       $(CXX) $(CXXFLAGS) $(LDFLAGS) $(PICFLAGS) $(LFLAGS) -o $@ \
+          $(LFLAGS_SO) $(SONAME_MAGIC)$($(@F)-SONAME) -shared \
+          $(filter %.opic,$^) $($(@F)-SLIBS)
+
+# Compilation rules
+vpath %.cc $(SUBDIRS)
+$(OBJ)/%.opic: %.cc
+       echo Compiling $< to $@
+       $(CXX) -c $(INLINEDEPFLAG) $(CPPFLAGS) $(CXXFLAGS) $(PICFLAGS) -o $@ $<
+       $(DoDep)
+
+# Include the dependencies that are available
+The_DFiles = $(wildcard $($(LOCAL)-DEP))
+ifneq ($(words $(The_DFiles)),0)
+include $(The_DFiles)
+endif 
diff --git a/tools/dsync-0.0/buildlib/makefile.in b/tools/dsync-0.0/buildlib/makefile.in
new file mode 100644 (file)
index 0000000..de2f70d
--- /dev/null
@@ -0,0 +1,41 @@
+# -*- make -*-
+
+# This is the build directory make file, it sets the build directory
+# and runs the src makefile.
+ifndef NOISY
+.SILENT:
+endif
+include environment.mak
+
+SRCDIR=@top_srcdir@
+DIRS:=./docs ./bin ./obj ./include 
+SUBDIRS:= $(DIRS)  ./include/dsync ./obj/libdsync ./obj/test ./obj/cmdline
+BUILD:=$(shell pwd)
+export BUILD
+
+# Chain to the parent make to do the actual building
+.PHONY: headers library clean veryclean all binary program doc \
+        veryclean/local
+all headers library clean veryclean binary program doc:
+       $(MAKE) -C $(SRCDIR) -f Makefile $@
+
+# Purge everything.
+.PHONY: maintainer-clean dist-clean pristine sanity distclean
+maintainer-clean dist-clean pristine sanity distclean:
+       -rm -rf $(DIRS)
+       -rm -f config.cache config.log config.status environment.mak makefile
+       
+# This makes any missing directories
+.PHONY: dirs
+MISSING_DIRS:= $(filter-out $(wildcard $(SUBDIRS)),$(SUBDIRS))
+dirs:
+ifneq ($(words $(MISSING_DIRS)),0)
+       @mkdir  $(MISSING_DIRS)
+else
+       @echo > /dev/null
+endif  
+ifeq ($(HAVE_C9X),yes)
+       -@rm include/inttypes.h > /dev/null 2>&1
+else
+       @cp $(SRCDIR)/buildlib/inttypes.h.in include/inttypes.h
+endif
diff --git a/tools/dsync-0.0/buildlib/manpage.mak b/tools/dsync-0.0/buildlib/manpage.mak
new file mode 100644 (file)
index 0000000..cfa5fc1
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- make -*-
+
+# This installs man pages into the doc directory
+
+# Input
+# $(SOURCE) - The documents to use
+
+# All output is writtin to files in the build doc directory
+
+# See defaults.mak for information about LOCAL
+
+# Some local definitions
+LOCAL := manpage-$(firstword $(SOURCE))
+$(LOCAL)-LIST := $(addprefix $(DOC)/,$(SOURCE))
+
+# Install generation hooks
+doc: $($(LOCAL)-LIST)
+veryclean: veryclean/$(LOCAL)
+
+$($(LOCAL)-LIST) : $(DOC)/% : %
+       echo Installing man page $< to $(@D)
+       cp $< $(@D)
+
+# Clean rule
+.PHONY: veryclean/$(LOCAL)
+veryclean/$(LOCAL):
+       -rm -rf $($(@F)-LIST)
diff --git a/tools/dsync-0.0/buildlib/mkChangeLog b/tools/dsync-0.0/buildlib/mkChangeLog
new file mode 100755 (executable)
index 0000000..c54a433
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+NAMES="`sed -ne 's/^.*CVS:\([^ ]\+\) \([^<]\+\) <\([^>]*\)>/\
+       -u '\''\1:\2:\3'\''/gp' AUTHORS`"
+OPTIONS="-l 78"
+
+# Generate the standard ChangeLog
+echo CVSIGNORE=po rcs2log $OPTIONS $NAMES
+eval CVSIGNORE=po rcs2log $OPTIONS $NAMES >> ChangeLog
+
+# Generate the po ChangeLog
+echo rcs2log $OPTIONS $NAMES po
+eval rcs2log $OPTIONS $NAMES po >> po/ChangeLog
diff --git a/tools/dsync-0.0/buildlib/program.mak b/tools/dsync-0.0/buildlib/program.mak
new file mode 100644 (file)
index 0000000..fe0d30d
--- /dev/null
@@ -0,0 +1,50 @@
+# -*- make -*-
+
+# This creates a program
+
+# Input
+# $(SOURCE) - The source code to use
+# $(PROGRAM) - The name of the program
+# $(SLIBS) - Shared libs to link against
+# $(LIB_MAKES) - Shared libary make files to depend on - to ensure we get
+# remade when the shared library version increases.
+
+# See defaults.mak for information about LOCAL
+
+# Some local definitions
+LOCAL := $(PROGRAM)
+$(LOCAL)-OBJS := $(addprefix $(OBJ)/,$(addsuffix .o,$(notdir $(basename $(SOURCE)))))
+$(LOCAL)-DEP := $(addprefix $(DEP)/,$(addsuffix .o.d,$(notdir $(basename $(SOURCE)))))
+$(LOCAL)-BIN := $(BIN)/$(PROGRAM)
+$(LOCAL)-SLIBS := $(SLIBS)
+$(LOCAL)-MKS := $(addprefix $(BASE)/,$(LIB_MAKES))
+
+# Install the command hooks
+program: $(BIN)/$(PROGRAM)
+clean: clean/$(LOCAL)
+veryclean: veryclean/$(LOCAL)
+
+# The clean rules
+.PHONY: clean/$(LOCAL) veryclean/$(LOCAL)
+clean/$(LOCAL):
+       -rm -f $($(@F)-OBJS) $($(@F)-DEP)
+veryclean/$(LOCAL): clean/$(LOCAL)
+       -rm -f $($(@F)-BIN)
+
+# The binary build rule
+$($(LOCAL)-BIN): $($(LOCAL)-OBJS) $($(LOCAL)-MKS)
+       echo Building program $@
+       $(CXX) $(CXXFLAGS) $(LDFLAGS) $(LFLAGS) -o $@ $(filter %.o,$^) $($(@F)-SLIBS) $(LEFLAGS)
+
+# Compilation rules
+vpath %.cc $(SUBDIRS)
+$(OBJ)/%.o: %.cc
+       echo Compiling $< to $@
+       $(CXX) -c $(INLINEDEPFLAG) $(CPPFLAGS) $(CXXFLAGS) -o $@ $<
+       $(DoDep)
+
+# Include the dependencies that are available
+The_DFiles = $(wildcard $($(LOCAL)-DEP))
+ifneq ($(words $(The_DFiles)),0)
+include $(The_DFiles)
+endif 
diff --git a/tools/dsync-0.0/buildlib/sizetable b/tools/dsync-0.0/buildlib/sizetable
new file mode 100644 (file)
index 0000000..b6dbca3
--- /dev/null
@@ -0,0 +1,19 @@
+#
+# This file lists common architectures for cross-compilation (CPUs, not
+# OSs), and the endian-ness and relative type sizes. It is not needed for
+# native compilation.
+#
+# If you wish to cross-compile APT, and your architecture is not listed
+# here, you should add it, and submit it by email to the APT team at
+# <apt@packages.debian.org>.
+#
+# This is used primarily for the MD5 algorithm.
+# The format is:-
+# CPU ':'      endian  sizeof: char, int, short, long
+i386:  little  1 4 2 4
+alpha:  little  1 4 2 8
+sparc:  big     1 4 2 4
+m68k:   big     1 4 2 4
+powerpc: big    1 4 2 4
+mipsel: little  1 4 2 4
+x86_64:        little  1 4 2 8
diff --git a/tools/dsync-0.0/buildlib/staticlibrary.mak b/tools/dsync-0.0/buildlib/staticlibrary.mak
new file mode 100644 (file)
index 0000000..998ca5b
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- make -*-
+
+# This creates a static library.
+
+# Input
+# $(SOURCE) - The source code to use
+# $(HEADERS) - Exported header files and private header files
+# $(LIBRARY) - The name of the library without lib or .so 
+
+# All output is writtin to .o files in the build directory
+
+# See defaults.mak for information about LOCAL
+
+# Some local definitions
+LOCAL := lib$(LIBRARY).a
+$(LOCAL)-OBJS := $(addprefix $(OBJ)/,$(addsuffix .o,$(notdir $(basename $(SOURCE)))))
+$(LOCAL)-DEP := $(addprefix $(DEP)/,$(addsuffix .o.d,$(notdir $(basename $(SOURCE)))))
+$(LOCAL)-HEADERS := $(addprefix $(INCLUDE)/,$(HEADERS))
+$(LOCAL)-LIB := $(LIB)/lib$(LIBRARY).a
+
+# Install the command hooks
+headers: $($(LOCAL)-HEADERS)
+library: $($(LOCAL)-LIB)
+clean: clean/$(LOCAL)
+veryclean: veryclean/$(LOCAL)
+
+# The clean rules
+.PHONY: clean/$(LOCAL) veryclean/$(LOCAL)
+clean/$(LOCAL):
+       -rm -f $($(@F)-OBJS) $($(@F)-DEP)
+veryclean/$(LOCAL): clean/$(LOCAL)
+       -rm -f $($(@F)-HEADERS) $($(@F)-LIB)
+
+# Build rules for the two symlinks
+.PHONY: $($(LOCAL)-LIB)
+       
+# The binary build rule
+$($(LOCAL)-LIB): $($(LOCAL)-HEADERS) $($(LOCAL)-OBJS)
+       echo Building library $@
+       -rm $@ > /dev/null 2>&1
+       $(AR) cq $@ $(filter %.o,$^)
+
+# Compilation rules
+vpath %.cc $(SUBDIRS)
+$(OBJ)/%.o: %.cc
+       echo Compiling $< to $@
+       $(CXX) -c $(INLINEDEPFLAG) $(CPPFLAGS) $(CXXFLAGS) -o $@ $<
+       $(DoDep)
+
+# Include the dependencies that are available
+The_DFiles = $(wildcard $($(LOCAL)-DEP))
+ifneq ($(words $(The_DFiles)),0)
+include $(The_DFiles)
+endif 
diff --git a/tools/dsync-0.0/buildlib/tools.m4 b/tools/dsync-0.0/buildlib/tools.m4
new file mode 100644 (file)
index 0000000..06f8770
--- /dev/null
@@ -0,0 +1,10 @@
+# tl_CHECK_TOOL_PREFIX will work _BEFORE_ AC_CANONICAL_HOST, etc., has been
+# called. It should be called again after these have been called.
+#
+# Basically we want to check if the host alias specified by the user is
+# different from the build alias. The rules work like this:-
+#
+# If host is not specified, it defaults to NONOPT
+# If build is not specified, it defaults to NONOPT
+# If nonopt is not specified, we guess all other values
+
diff --git a/tools/dsync-0.0/buildlib/yodl_manpage.mak b/tools/dsync-0.0/buildlib/yodl_manpage.mak
new file mode 100644 (file)
index 0000000..a5f436f
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- make -*-
+
+# This handles man pages in YODL format. We convert to the respective
+# output in the source directory then copy over to the final dest. This
+# means yodl is only needed if compiling from CVS
+
+# Input
+# $(SOURCE) - The documents to use, in the form foo.sect, ie apt-cache.8
+#             the yodl files are called apt-cache.8.yo
+
+# See defaults.mak for information about LOCAL
+
+# Some local definitions
+ifdef YODL_MAN
+
+LOCAL := yodl-manpage-$(firstword $(SOURCE))
+$(LOCAL)-LIST := $(SOURCE)
+
+# Install generation hooks
+doc: $($(LOCAL)-LIST)
+veryclean: veryclean/$(LOCAL)
+
+$($(LOCAL)-LIST) :: % : %.yo
+       echo Creating man page $@
+       yodl2man -o $@ $<
+
+# Clean rule
+.PHONY: veryclean/$(LOCAL)
+veryclean/$(LOCAL):
+       -rm -rf $($(@F)-LIST)
+
+else
+
+# Strip from the source list any man pages we dont have compiled already
+SOURCE := $(wildcard $(SOURCE))
+
+endif
+
+# Chain to the manpage rule
+ifneq ($(words $(SOURCE)),0)
+include $(MANPAGE_H)
+endif
diff --git a/tools/dsync-0.0/cmdline/dsync-cdimage.cc b/tools/dsync-0.0/cmdline/dsync-cdimage.cc
new file mode 100644 (file)
index 0000000..74e9128
--- /dev/null
@@ -0,0 +1,174 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: dsync-cdimage.cc,v 1.2 1999/12/26 06:59:00 jgg Exp $
+/* ######################################################################
+
+   DSync CD Image - CD Image transfer program
+   
+   This implements the DSync CD transfer method. This method is optimized
+   to reconstruct a CD from a mirror of the CD's contents and the original
+   ISO image.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#include <dsync/cmndline.h>
+#include <dsync/configuration.h>
+#include <dsync/error.h>
+#include <dsync/filelistdb.h>
+#include <dsync/rsync-algo.h>
+#include <config.h>
+
+#include <iostream>
+#include <fstream>
+#include <signal.h>
+using namespace std;
+                                                                       /*}}}*/
+
+// Externs                                                             /*{{{*/
+ostream c0out(cout.rdbuf());
+ostream c1out(cout.rdbuf());
+ostream c2out(cout.rdbuf());
+ofstream devnull("/dev/null");
+unsigned int ScreenWidth = 80;
+                                                                       /*}}}*/
+
+// DoGenerate - Generate the checksum list                             /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool DoGenerate(CommandLine &CmdL)
+{
+   return true;
+}
+                                                                       /*}}}*/
+// DoAggregate - Generate aggregated file records                      /*{{{*/
+// ---------------------------------------------------------------------
+/* This takes a file list with already generated rsync checksums and builds
+   aggregated file lists for each checksum record */
+bool DoAggregate(CommandLine &CmdL)
+{
+   if (CmdL.FileList[1] == 0)
+      return _error->Error("You must specify a file name");
+   
+   // Open the file
+   dsMMapIO IO(CmdL.FileList[1]);
+   if (_error->PendingError() == true)
+      return false;
+   
+   dsFList List;
+   if (List.Step(IO) == false || List.Tag != dsFList::tHeader)
+      return _error->Error("Unable to read header");
+   
+   string Dir;
+   string File;
+   while (List.Step(IO) == true)
+   {
+      if (List.Tag == dsFList::tDirStart)
+      {
+        Dir = List.Dir.Name;
+        continue;
+      }
+      
+      if (List.Entity != 0)
+      {
+        File = List.Entity->Name;
+        continue;
+      }
+      
+      if (List.Tag == dsFList::tRSyncChecksum)
+      {
+        RSyncMatch Match(List.RChk);
+      }
+      
+      if (List.Tag == dsFList::tTrailer)
+        break;
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+// ShowHelp - Show the help screen                                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool ShowHelp(CommandLine &CmdL)
+{
+   cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
+      " compiled on " << __DATE__ << "  " << __TIME__ << endl;
+   
+   cout << 
+      "Usage: dsync-cdimage [options] command [file]\n"
+      "\n"
+      "dsync-cdimage is a tool for replicating CD images from a mirror of\n"
+      "their contents.\n"
+      "\n"
+      "Commands:\n"
+      "   generate - Build a file+checksum index\n"
+      "   help - This help text\n"
+      "   verify - Compare the index against files in the current directory\n"
+      "\n"
+      "Options:\n"
+      "  -h  This help text.\n"
+      "  -q  Loggable output - no progress indicator\n"
+      "  -qq No output except for errors\n"
+      "  -c=? Read this configuration file\n"
+      "  -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp\n"
+      "See the dsync-cdimage(1) and dsync.conf(5) manual\n"
+      "pages for more information." << endl;
+   return 100;
+}
+                                                                       /*}}}*/
+
+int main(int argc, const char *argv[])
+{
+   CommandLine::Args Args[] = {
+      {'h',"help","help",0},
+      {'q',"quiet","quiet",CommandLine::IntLevel},
+      {'q',"silent","quiet",CommandLine::IntLevel},
+      {'v',"verbose","verbose",CommandLine::IntLevel},
+      {'c',"config-file",0,CommandLine::ConfigFile},
+      {'o',"option",0,CommandLine::ArbItem},
+      {0,0,0,0}};
+   CommandLine::Dispatch Cmds[] = {{"generate",&DoGenerate},
+                                   {"help",&ShowHelp},
+                                   {"aggregate",&DoAggregate},
+                                   {0,0}};
+   CommandLine CmdL(Args,_config);
+   if (CmdL.Parse(argc,argv) == false)
+   {
+      _error->DumpErrors();
+      return 100;
+   }
+   
+   // See if the help should be shown
+   if (_config->FindB("help") == true ||
+       CmdL.FileSize() == 0)
+      return ShowHelp(CmdL);   
+
+   // Setup the output streams
+   c0out.rdbuf(cout.rdbuf());
+   c1out.rdbuf(cout.rdbuf());
+   c2out.rdbuf(cout.rdbuf());
+   if (_config->FindI("quiet",0) > 0)
+      c0out.rdbuf(devnull.rdbuf());
+   if (_config->FindI("quiet",0) > 1)
+      c1out.rdbuf(devnull.rdbuf());
+
+   // Setup the signals
+/*   signal(SIGWINCH,SigWinch);
+   SigWinch(0);*/
+   
+   // Match the operation
+   CmdL.DispatchArg(Cmds);
+   
+   // Print any errors or warnings found during parsing
+   if (_error->empty() == false)
+   {
+      
+      bool Errors = _error->PendingError();
+      _error->DumpErrors();
+      return Errors == true?100:0;
+   }
+         
+   return 0; 
+}
diff --git a/tools/dsync-0.0/cmdline/dsync-flist.cc b/tools/dsync-0.0/cmdline/dsync-flist.cc
new file mode 100644 (file)
index 0000000..e9ebb28
--- /dev/null
@@ -0,0 +1,1097 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: dsync-flist.cc,v 1.27 1999/12/26 06:59:00 jgg Exp $
+/* ######################################################################
+
+   Dsync FileList is a tool to manipulate and generate the dsync file 
+   listing
+   
+   Several usefull functions are provided, the most notable is to generate
+   the file list and to dump it. There is also a function to compare the
+   file list against a local directory tree.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync-flist.h"
+#endif 
+
+#include "dsync-flist.h"
+#include <dsync/cmndline.h>
+#include <dsync/error.h>
+#include <dsync/md5.h>
+#include <dsync/strutl.h>
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <utime.h>
+#include <unistd.h>
+#include <termios.h>
+#include <signal.h>
+
+#include <iostream>
+using namespace std;
+
+                                                                       /*}}}*/
+
+// Externs                                                             /*{{{*/
+ostream c0out(cout.rdbuf());
+ostream c1out(cout.rdbuf());
+ostream c2out(cout.rdbuf());
+ofstream devnull("/dev/null");
+unsigned int ScreenWidth = 80;
+                                                                       /*}}}*/
+
+// Progress::Progress - Constructor                                    /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+Progress::Progress()
+{
+   Quiet = false;
+   if (_config->FindI("quiet",0) > 0)
+      Quiet = true;
+   DirCount = 0;
+   FileCount = 0;
+   LinkCount = 0;
+   Bytes = 0;
+   CkSumBytes = 0;
+   gettimeofday(&StartTime,0);
+}
+                                                                       /*}}}*/
+// Progress::Done - Clear the progress meter                           /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void Progress::Done()
+{
+   if (Quiet == false)
+      c0out << '\r' << BlankLine << '\r' << flush;
+   BlankLine[0] = 0;
+}
+                                                                       /*}}}*/
+// Progress::ElaspedTime - Return the time that has elapsed            /*{{{*/
+// ---------------------------------------------------------------------
+/* Computes the time difference with maximum accuracy */
+double Progress::ElapsedTime()
+{
+   // Compute the CPS and elapsed time
+   struct timeval Now;
+   gettimeofday(&Now,0);
+
+   return Now.tv_sec - StartTime.tv_sec + (Now.tv_usec - 
+                                          StartTime.tv_usec)/1000000.0;
+}
+                                                                       /*}}}*/
+// Progress::Update - Update the meter                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void Progress::Update(const char *Directory)
+{
+   LastCount = DirCount+LinkCount+FileCount;
+   
+   if (Quiet == true)
+      return;
+
+   // Put the number of files and bytes at the end of the meter
+   char S[1024];
+   if (ScreenWidth > sizeof(S)-1)
+      ScreenWidth = sizeof(S)-1;
+   
+   unsigned int Len = snprintf(S,sizeof(S),"|%lu %sb",
+                              DirCount+LinkCount+FileCount,
+                              SizeToStr(Bytes).c_str());
+   
+   memmove(S + (ScreenWidth - Len),S,Len+1);
+   memset(S,' ',ScreenWidth - Len);
+   
+   // Put the directory name at the front, possibly shortened
+   if (Directory == 0 || Directory[0] == 0)
+      S[snprintf(S,sizeof(S),"<root>")] = ' ';
+   else
+   {
+      // If the path is too long fix it and prefix it with '...'
+      if (strlen(Directory) >= ScreenWidth - Len - 1)
+      {
+        S[snprintf(S,sizeof(S),"%s",Directory + 
+                   strlen(Directory) - ScreenWidth + Len + 1)] = ' ';
+        S[0] = '.'; S[1] = '.'; S[2] = '.';
+      }
+      else
+        S[snprintf(S,sizeof(S),"%s",Directory)] = ' ';
+   }
+   
+   strcpy(LastLine,S);
+   c0out << S << '\r' << flush;
+   memset(BlankLine,' ',strlen(S));
+   BlankLine[strlen(S)] = 0;
+}
+                                                                       /*}}}*/
+// Progress::Stats - Show a statistics report                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void Progress::Stats(bool CkSum)
+{
+   // Display some interesting statistics
+   double Elapsed = ElapsedTime();
+   c1out << DirCount << " directories, " << FileCount <<
+      " files and " << LinkCount << " links (" << 
+      (DirCount+FileCount+LinkCount) << "). ";
+   if (CkSum == true)
+   {
+      if (CkSumBytes == Bytes)
+        c1out << "Total Size is " << SizeToStr(Bytes) << "b. ";
+      else
+        c1out << SizeToStr(CkSumBytes) << '/' <<
+           SizeToStr(Bytes) << "b hashed.";
+   }   
+   else
+      c1out << "Total Size is " << SizeToStr(Bytes) << "b. ";
+      
+   c1out << endl;
+   c1out << "Elapsed time " <<  TimeToStr((long)Elapsed) <<
+      " (" << SizeToStr((DirCount+FileCount+LinkCount)/Elapsed) <<
+      " files/sec) ";
+   if (CkSumBytes != 0)
+      c1out << " (" << SizeToStr(CkSumBytes/Elapsed) << "b/s hash)";
+   c1out << endl;
+}
+                                                                       /*}}}*/
+
+// ListGenerator::ListGenerator - Constructor                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+ListGenerator::ListGenerator()
+{
+   Act = !_config->FindB("noact",false);
+   StripDepth = _config->FindI("FileList::CkSum-PathStrip",0);
+   Verbose = false;
+   if (_config->FindI("verbose",0) > 0)
+      Verbose = true;
+   DB = 0;
+   DBIO = 0;
+
+   // Set RSync checksum limits
+   MinRSyncSize = _config->FindI("FileList::MinRSyncSize",0);
+   if (MinRSyncSize == 0)
+      MinRSyncSize = 1;
+   if (_config->FindB("FileList::RSync-Hashes",false) == false)
+       MinRSyncSize = 0;
+       
+   // Load the rsync filter
+   if (RSyncFilter.LoadFilter(_config->Tree("FList::RSync-Filter")) == false)
+      return;
+       
+   // Load the clean filter
+   if (RemoveFilter.LoadFilter(_config->Tree("FList::Clean-Filter")) == false)
+      return;
+}
+                                                                       /*}}}*/
+// ListGenerator::~ListGenerator - Destructor                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+ListGenerator::~ListGenerator()
+{
+   delete DB;
+   delete DBIO;
+}
+                                                                       /*}}}*/
+// ListGenerator::Visit - Collect statistics about the tree            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+int ListGenerator::Visit(const char *Directory,const char *File,
+                        struct stat const &Stat)
+{
+   if (Prog.DirCount+Prog.LinkCount+Prog.FileCount - Prog.LastCount > 100 ||
+       File == 0)
+      Prog.Update(Directory);
+   
+   // Ignore directory enters
+   if (File == 0)
+      return 0;
+   
+   // Increment our counters
+   if (S_ISDIR(Stat.st_mode) != 0)
+      Prog.DirCount++;
+   else
+   {
+      if (S_ISLNK(Stat.st_mode) != 0)
+        Prog.LinkCount++;
+      else
+        Prog.FileCount++;
+   }
+   
+   // Normal file
+   if (S_ISREG(Stat.st_mode) != 0)
+      Prog.Bytes += Stat.st_size;
+   
+   // Look for files to erase
+   if (S_ISDIR(Stat.st_mode) == 0 &&
+       RemoveFilter.Test(Directory,File) == false)
+   {
+      Prog.Hide();
+      c1out << "Unlinking " << Directory << File << endl;
+      Prog.Show();
+      
+      if (Act == true && unlink(File) != 0)
+      {
+        _error->Errno("unlink","Failed to remove %s%s",Directory,File);
+        return -1;
+      }
+      
+      return 1;
+   }
+   
+   return 0;
+}                       
+                                                                       /*}}}*/
+// ListGenerator::EmitMD5 - Perform md5 lookup caching                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This looks up the file in the cache to see if it is one we already 
+   know the hash too */
+bool ListGenerator::EmitMD5(const char *Dir,const char *File,
+                           struct stat const &St,unsigned char MD5[16],
+                           unsigned int Tag,unsigned int Flag)
+{
+   if ((IO->Header.Flags[Tag] & Flag) != Flag)
+      return true;
+
+   // Lookup the md5 in the old file list
+   if (DB != 0 && (DBIO->Header.Flags[Tag] & Flag) == Flag)
+   {
+      // Do a lookup and make sure the timestamps match
+      dsFList List;
+      bool Hit = false;
+      const char *iDir = Dir;
+      unsigned int Strip = StripDepth;
+      while (true)
+      {         
+        if (DB->Lookup(*DBIO,iDir,File,List) == true && List.Entity != 0)
+        {
+           if ((signed)(List.Entity->ModTime + List.Head.Epoch) == St.st_mtime)
+              Hit = true;          
+           break;
+        }
+        
+        if (Strip == 0)
+           break;
+        
+        Strip--;
+        for (; *iDir != 0 && *iDir != '/'; iDir++);
+        if (*iDir == 0 || iDir[1] == 0)
+           break;
+        iDir++;
+      }
+        
+      if (Hit == true)
+      {
+        /* Both hardlinks and normal files have md5s, also check that the
+           sizes match */
+        if (List.File != 0 && List.File->Size == (unsigned)St.st_size)
+        {
+           memcpy(MD5,List.File->MD5,sizeof(List.File->MD5));
+           return true;
+        }
+      }      
+   }
+   
+   Prog.CkSumBytes += St.st_size;
+   
+   if (Verbose == true)
+   {
+      Prog.Hide();
+      c1out << "MD5 " << Dir << File << endl;
+      Prog.Show();
+   }
+   
+   return dsGenFileList::EmitMD5(Dir,File,St,MD5,Tag,Flag);
+}
+                                                                       /*}}}*/
+// ListGenerator::NeedsRSync - Check if a file is rsyncable            /*{{{*/
+// ---------------------------------------------------------------------
+/* This checks the rsync filter list and the rsync size limit*/
+bool ListGenerator::NeedsRSync(const char *Dir,const char *File,
+                              dsFList::NormalFile &F)
+{
+   if (MinRSyncSize == 0)
+      return false;
+   
+   if (F.Size <= MinRSyncSize)
+      return false;
+   
+   if (RSyncFilter.Test(Dir,File) == false)
+      return false;
+   
+   /* Add it to the counters, EmitMD5 will not be called if rsync checksums
+      are being built. */
+   Prog.CkSumBytes += F.Size;  
+   if (Verbose == true)
+   {
+      Prog.Hide();
+      c1out << "RSYNC " << Dir << File << endl;
+      Prog.Show();
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+// Compare::Compare - Constructor                                      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+Compare::Compare()
+{
+   Verbose = false;
+   if (_config->FindI("verbose",0) > 0)
+      Verbose = true;
+   Act = !_config->FindB("noact",false);
+   DoDelete = _config->FindB("delete",false);
+}
+                                                                       /*}}}*/
+// Compare::Visit - Collect statistics about the tree                  /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool Compare::Visit(dsFList &List,string Dir)
+{
+   if (Prog.DirCount+Prog.LinkCount+Prog.FileCount - Prog.LastCount > 100 ||
+       List.Tag == dsFList::tDirStart)
+      Prog.Update(Dir.c_str());
+   
+   // Increment our counters
+   if (List.Tag == dsFList::tDirectory)
+      Prog.DirCount++;
+   else
+   {
+      if (List.Tag == dsFList::tSymlink)
+        Prog.LinkCount++;
+
+      if (List.Tag == dsFList::tNormalFile || 
+         List.Tag == dsFList::tHardLink ||
+         List.Tag == dsFList::tDeviceSpecial)
+        Prog.FileCount++;
+   }
+   
+   // Normal file
+   if (List.File != 0)
+      Prog.Bytes += List.File->Size;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// Compare::PrintPath - Print out a path string                                /*{{{*/
+// ---------------------------------------------------------------------
+/* This handles the absolute paths that can occure while processing */
+void Compare::PrintPath(ostream &out,string Dir,string Name)
+{
+   if (Name[0] != '/')
+      out << Dir << Name << endl;
+   else
+      out << string(Name,Base.length()) << endl;
+}
+                                                                       /*}}}*/
+
+// LookupPath - Find a full path within the database                   /*{{{*/
+// ---------------------------------------------------------------------
+/* This does the necessary path simplification and symlink resolution
+   to locate the path safely. The file must exist locally inorder to 
+   resolve the local symlinks. */
+bool LookupPath(const char *Path,dsFList &List,dsFileListDB &DB,
+               dsFList::IO &IO)
+{
+   char Buffer[2024];
+   strcpy(Buffer,Path);
+      
+   if (SimplifyPath(Buffer) == false || 
+       ResolveLink(Buffer,sizeof(Buffer)) == false)
+      return false;
+   
+   // Strip off the final component name
+   char *I = Buffer + strlen(Buffer);
+   for (; I != Buffer && (*I == '/' || *I == 0); I--);
+   for (; I != Buffer && *I != '/'; I--);
+   if (I != Buffer)
+   {
+      memmove(I+1,I,strlen(I) + 1);
+      I++;
+      *I = 0;
+      I++;
+      if (DB.Lookup(IO,Buffer,I,List) == false)
+        return false;
+   }
+   else
+   {
+      if (DB.Lookup(IO,"",I,List) == false)
+        return false;
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// PrintMD5 - Prints the MD5 of a file in the form similar to md5sum   /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void PrintMD5(dsFList &List,const char *Dir,const char *File = 0)
+{
+   if (List.File == 0 || 
+       List.Head.Flags[List.Tag] & dsFList::NormalFile::FlMD5 == 0)
+      return;
+
+   char S[16*2+1];
+   for (unsigned int I = 0; I != 16; I++)
+      sprintf(S+2*I,"%02x",List.File->MD5[I]);
+   S[16*2] = 0;
+   if (File == 0)
+      cout << S << "  " << Dir << List.File->Name << endl;
+   else
+      cout << S << "  " << File << endl;
+}
+                                                                       /*}}}*/
+
+// DoGenerate - The Generate Command                                   /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool DoGenerate(CommandLine &CmdL)
+{
+   ListGenerator Gen;
+   if (_error->PendingError() == true)
+      return false;
+   
+   // Load the filter list
+   if (Gen.Filter.LoadFilter(_config->Tree("FileList::Filter")) == false)
+      return false;
+
+   // Load the delay filter list
+   if (Gen.PreferFilter.LoadFilter(_config->Tree("FileList::Prefer-Filter")) == false)
+      return false;
+   
+   // Determine the ordering to use
+   string Ord = _config->Find("FileList::Order","tree");
+   if (stringcasecmp(Ord,"tree") == 0)
+      Gen.Type = dsGenFileList::Tree;
+   else
+   {
+      if (stringcasecmp(Ord,"breadth") == 0)
+        Gen.Type = dsGenFileList::Breadth;
+      else
+      {
+         if (stringcasecmp(Ord,"depth") == 0)
+           Gen.Type = dsGenFileList::Depth;
+        else
+           return _error->Error("Invalid ordering %s, must be tree, breadth or detph",Ord.c_str());
+      }      
+   }
+
+   if (CmdL.FileList[1] == 0)
+      return _error->Error("You must specify a file name");
+   
+   string List = CmdL.FileList[1];
+   
+   // Open the original file to pull cached Check Sums out of
+   if (FileExists(List) == true && 
+       _config->FindB("FileList::MD5-Hashes",false) == true)
+   {
+      Gen.DBIO = new dsMMapIO(List);
+      if (_error->PendingError() == true)
+        return false;
+      Gen.DB = new dsFileListDB;
+      if (Gen.DB->Generate(*Gen.DBIO) == false)
+        return false;
+   }   
+
+   // Sub scope to close the file
+   {      
+      FdIO IO(List + ".new",FileFd::WriteEmpty);
+      
+      // Set the flags for the list
+      if (_config->FindB("FileList::MD5-Hashes",false) == true)
+      {
+        IO.Header.Flags[dsFList::tNormalFile] |= dsFList::NormalFile::FlMD5;
+        IO.Header.Flags[dsFList::tHardLink] |= dsFList::HardLink::FlMD5;
+      }
+      if (_config->FindB("FileList::Permissions",false) == true)
+      {
+        IO.Header.Flags[dsFList::tDirectory] |= dsFList::Directory::FlPerm;
+        IO.Header.Flags[dsFList::tNormalFile] |= dsFList::NormalFile::FlPerm;
+        IO.Header.Flags[dsFList::tHardLink] |= dsFList::HardLink::FlPerm;
+      }
+      if (_config->FindB("FileList::Ownership",false) == true)
+      {
+        IO.Header.Flags[dsFList::tDirectory] |= dsFList::Directory::FlOwner;
+        IO.Header.Flags[dsFList::tNormalFile] |= dsFList::NormalFile::FlOwner;
+        IO.Header.Flags[dsFList::tSymlink] |= dsFList::Symlink::FlOwner;
+        IO.Header.Flags[dsFList::tDeviceSpecial] |= dsFList::DeviceSpecial::FlOwner;
+        IO.Header.Flags[dsFList::tHardLink] |= dsFList::HardLink::FlOwner;
+      }
+      
+      if (Gen.Go("./",IO) == false)
+        return false;
+      Gen.Prog.Done();
+      Gen.Prog.Stats(_config->FindB("FileList::MD5-Hashes",false));
+      
+      delete Gen.DB;
+      Gen.DB = 0;
+      delete Gen.DBIO;
+      Gen.DBIO = 0;
+   }
+   
+   // Just in case :>
+   if (_error->PendingError() == true)
+      return false;
+   
+   // Swap files
+   bool OldExists = FileExists(List);
+   if (OldExists == true && rename(List.c_str(),(List + "~").c_str()) != 0)
+      return _error->Errno("rename","Unable to rename %s to %s~",List.c_str(),List.c_str());
+   if (rename((List + ".new").c_str(),List.c_str()) != 0)
+      return _error->Errno("rename","Unable to rename %s.new to %s",List.c_str(),List.c_str());
+   if (OldExists == true && unlink((List + "~").c_str()) != 0)
+      return _error->Errno("unlink","Unable to unlink %s~",List.c_str());
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DoDump - Dump the contents of a file list                           /*{{{*/
+// ---------------------------------------------------------------------
+/* This displays a short one line dump of each record in the file */
+bool DoDump(CommandLine &CmdL)
+{
+   if (CmdL.FileList[1] == 0)
+      return _error->Error("You must specify a file name");
+   
+   // Open the file
+   dsMMapIO IO(CmdL.FileList[1]);
+   if (_error->PendingError() == true)
+      return false;
+   
+   dsFList List;
+   unsigned long CountDir = 0;
+   unsigned long CountFile = 0;
+   unsigned long CountLink = 0;
+   unsigned long CountLinkReal = 0;
+   unsigned long NumFiles = 0;
+   unsigned long NumDirs = 0;
+   unsigned long NumLinks = 0;
+   double Bytes = 0;
+   
+   while (List.Step(IO) == true)
+   {
+      if (List.Print(cout) == false)
+        return false;
+
+      switch (List.Tag)
+      {
+        case dsFList::tDirMarker:
+        case dsFList::tDirStart:
+        case dsFList::tDirectory:
+        {
+           CountDir += List.Dir.Name.length();
+           if (List.Tag == dsFList::tDirectory)
+              NumDirs++;
+           break;
+        }
+
+        case dsFList::tHardLink:
+        case dsFList::tNormalFile:
+        {
+           CountFile += List.File->Name.length();
+           NumFiles++;
+           Bytes += List.File->Size;
+           break;
+        }
+        
+        case dsFList::tSymlink:
+        {
+           CountFile += List.SLink.Name.length();
+           CountLink += List.SLink.To.length();
+           
+           unsigned int Tmp = List.SLink.To.length();
+           if ((List.SLink.Compress & (1<<7)) == (1<<7))
+              Tmp -= List.SLink.Name.length();
+           Tmp -= List.SLink.Compress & 0x7F;
+           CountLinkReal += Tmp;
+           NumLinks++;
+           break;
+        }
+      }
+      if (List.Tag == dsFList::tTrailer)
+        break;
+   }
+   cout << "String Sizes: Dirs=" << CountDir << " Files=" << CountFile << 
+      " Links=" << CountLink << " (" << CountLinkReal << ")";
+   cout << " Total=" << CountDir+CountFile+CountLink << endl;
+   cout << "Entries: Dirs=" << NumDirs << " Files=" << NumFiles << 
+      " Links=" << NumLinks << " Total=" << NumDirs+NumFiles+NumLinks << endl;
+   cout << "Totals " << SizeToStr(Bytes) << "b." << endl;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DoMkHardLinks - Generate hardlinks for duplicated files             /*{{{*/
+// ---------------------------------------------------------------------
+/* This scans the archive for any duplicated files, it uses the MD5 of each
+   file and searches a map for another match then links the two */
+struct Md5Cmp
+{
+   unsigned char MD5[16];
+   int operator <(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) < 0;};
+   int operator <=(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) <= 0;};
+   int operator >=(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) >= 0;};
+   int operator >(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) > 0;};
+   int operator ==(const Md5Cmp &rhs) const {return memcmp(MD5,rhs.MD5,sizeof(MD5)) == 0;};
+   
+   Md5Cmp(unsigned char Md[16]) {memcpy(MD5,Md,sizeof(MD5));};
+};
+
+struct Location
+{
+   string Dir;
+   string File;
+   
+   Location() {};
+   Location(string Dir,string File) : Dir(Dir), File(File) {};
+};
+
+bool DoMkHardLinks(CommandLine &CmdL)
+{
+   if (CmdL.FileList[1] == 0)
+      return _error->Error("You must specify a file name");
+   
+   // Open the file
+   dsMMapIO IO(CmdL.FileList[1]);
+   if (_error->PendingError() == true)
+      return false;
+
+   dsFList List;
+   if (List.Step(IO) == false || List.Tag != dsFList::tHeader)
+      return _error->Error("Unable to read header");
+
+   // Make sure we have hashes
+   if ((IO.Header.Flags[dsFList::tNormalFile] & 
+       dsFList::NormalFile::FlMD5) == 0 ||
+       (IO.Header.Flags[dsFList::tHardLink] & 
+       dsFList::HardLink::FlMD5) == 0)
+      return _error->Error("The file list must contain MD5 hashes");
+   
+   string LastDir;
+   double Savings = 0;
+   unsigned long Hits = 0;
+   bool Act = !_config->FindB("noact",false);   
+   map<Md5Cmp,Location> Map;   
+   while (List.Step(IO) == true)
+   {
+      // Entering a new directory, just store it..
+      if (List.Tag == dsFList::tDirStart)
+      {
+        LastDir = List.Dir.Name;
+        continue;
+      }
+
+      /* Handle normal file entities. Pre-existing hard links we treat
+         exactly like a normal file, if two hard link chains are identical
+        one will be destroyed and its items placed on the other 
+        automatcially */
+      if (List.File != 0)
+      {
+        map<Md5Cmp,Location>::const_iterator I = Map.find(Md5Cmp(List.File->MD5));
+        if (I == Map.end())
+        {
+           Map[Md5Cmp(List.File->MD5)] = Location(LastDir,List.File->Name);
+           continue;
+        }
+
+        // Compute full file names for both
+        string FileA = (*I).second.Dir + (*I).second.File;
+        struct stat StA;
+        string FileB = LastDir + List.File->Name;
+        struct stat StB;
+        
+        // Stat them
+        if (lstat(FileA.c_str(),&StA) != 0)
+        {
+           _error->Warning("Unable to stat %s",FileA.c_str());
+           continue;
+        }       
+        if (lstat(FileB.c_str(),&StB) != 0)
+        {
+           _error->Warning("Unable to stat %s",FileB.c_str());
+           continue;
+        }
+        
+        // Verify they are on the same filesystem
+        if (StA.st_dev != StB.st_dev || StA.st_size != StB.st_size)
+           continue;
+        
+        // And not merged..
+        if (StA.st_ino == StB.st_ino)
+           continue;
+        
+        c1out << "Dup " << FileA << endl;
+         c1out << "    " << FileB << endl;
+      
+        // Relink the file and copy the mod time from the oldest one.
+        if (Act == true)
+        {
+           if (unlink(FileB.c_str()) != 0)
+              return _error->Errno("unlink","Failed to unlink %s",FileB.c_str());
+           if (link(FileA.c_str(),FileB.c_str()) != 0)
+              return _error->Errno("link","Failed to link %s to %s",FileA.c_str(),FileB.c_str());
+           if (StB.st_mtime > StA.st_mtime)
+           {
+              struct utimbuf Time;
+              Time.actime = Time.modtime = StB.st_mtime;
+              if (utime(FileB.c_str(),&Time) != 0)
+                 _error->Warning("Unable to set mod time for %s",FileB.c_str());              
+           }
+        }
+        
+        // Counters
+        Savings += List.File->Size;
+        Hits++;
+        
+        continue;
+      }
+      
+      if (List.Tag == dsFList::tTrailer)
+        break;
+   }
+   
+   cout << "Total space saved by merging " << 
+      SizeToStr(Savings) << "b. " << Hits << " files affected." << endl;
+   return true;
+}
+                                                                       /*}}}*/
+// DoLookup - Lookup a single file in the listing                      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool DoLookup(CommandLine &CmdL)
+{
+   if (CmdL.FileSize() < 4)
+      return _error->Error("You must specify a file name, directory name and a entry");
+   
+   // Open the file
+   dsMMapIO IO(CmdL.FileList[1]);
+   if (_error->PendingError() == true)
+      return false;
+
+   // Index it
+   dsFileListDB DB;
+   if (DB.Generate(IO) == false)
+      return false;
+
+   dsFList List;
+   if (DB.Lookup(IO,CmdL.FileList[2],CmdL.FileList[3],List) == false)
+      return _error->Error("Unable to locate item");
+   List.Print(cout);
+   return true;
+}
+                                                                       /*}}}*/
+// DoMD5Cache - Lookup a stream of files in the listing                        /*{{{*/
+// ---------------------------------------------------------------------
+/* This takes a list of files names and prints out their MD5s, if possible
+   data is used from the cache to save IO */
+bool DoMD5Cache(CommandLine &CmdL)
+{
+   struct timeval Start;
+   gettimeofday(&Start,0);
+   
+   if (CmdL.FileList[1] == 0)
+      return _error->Error("You must specify a file name");
+   
+   // Open the file
+   dsMMapIO IO(CmdL.FileList[1]);
+   if (_error->PendingError() == true)
+      return false;
+
+   dsFList List;
+   if (List.Step(IO) == false || List.Tag != dsFList::tHeader)
+      return _error->Error("Unable to read header");
+   
+   // Make sure we have hashes
+   if ((IO.Header.Flags[dsFList::tNormalFile] & 
+       dsFList::NormalFile::FlMD5) == 0 ||
+       (IO.Header.Flags[dsFList::tHardLink] & 
+       dsFList::HardLink::FlMD5) == 0)
+      return _error->Error("The file list must contain MD5 hashes");
+
+   // Index it
+   dsFileListDB DB;
+   if (DB.Generate(IO) == false)
+      return false;
+
+   // Counters
+   double Bytes = 0;
+   double MD5Bytes = 0;
+   unsigned long Files = 0;
+   unsigned long Errors = 0;
+
+   while (!cin == false)
+   {
+      char Buf2[200];
+      cin.getline(Buf2,sizeof(Buf2));
+      if (Buf2[0] == 0)
+        continue;
+      Files++;
+      
+      // Stat the file
+      struct stat St;
+      if (stat(Buf2,&St) != 0)
+      {
+        cout << "<ERROR> " << Buf2 << "(stat)" << endl;
+        Errors++;
+        continue;
+      }
+            
+      // Lookup in the cache and make sure the file has not changed
+      if (LookupPath(Buf2,List,DB,IO) == false ||
+         (signed)(List.Entity->ModTime + List.Head.Epoch) != St.st_mtime ||
+         (List.File != 0 && List.File->Size != (unsigned)St.st_size))
+      {
+        _error->DumpErrors();
+        
+        // Open the file and hash it
+        MD5Summation Sum;
+        FileFd Fd(Buf2,FileFd::ReadOnly);
+        if (_error->PendingError() == true)
+        {
+           cout << "<ERROR> " << Buf2 << "(open)" << endl;
+           continue;
+        }
+        
+        if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
+        {
+           cout << "<ERROR> " << Buf2 << "(md5)" << endl;
+           continue;
+        }
+                
+        // Store the new hash
+        List.Tag = dsFList::tNormalFile;
+        Sum.Result().Value(List.File->MD5);
+        List.File->Size = (unsigned)St.st_size;
+        
+        MD5Bytes += List.File->Size;
+      }
+
+      PrintMD5(List,0,Buf2);
+      Bytes += List.File->Size;
+   }
+
+   // Print out a summary
+   struct timeval Now;
+   gettimeofday(&Now,0);
+   double Delta = Now.tv_sec - Start.tv_sec + (Now.tv_usec - Start.tv_usec)/1000000.0;
+   cerr << Files << " files, " << SizeToStr(MD5Bytes) << "/" << 
+      SizeToStr(Bytes) << " MD5'd, " << TimeToStr((unsigned)Delta) << endl;;
+      
+   return true;
+}
+                                                                       /*}}}*/
+// DoMD5Dump - Dump the md5 list                                       /*{{{*/
+// ---------------------------------------------------------------------
+/* This displays a short one line dump of each record in the file */
+bool DoMD5Dump(CommandLine &CmdL)
+{
+   if (CmdL.FileList[1] == 0)
+      return _error->Error("You must specify a file name");
+   
+   // Open the file
+   dsMMapIO IO(CmdL.FileList[1]);
+   if (_error->PendingError() == true)
+      return false;
+   
+   dsFList List;
+   if (List.Step(IO) == false || List.Tag != dsFList::tHeader)
+      return _error->Error("Unable to read header");
+   
+   // Make sure we have hashes
+   if ((IO.Header.Flags[dsFList::tNormalFile] & 
+       dsFList::NormalFile::FlMD5) == 0 ||
+       (IO.Header.Flags[dsFList::tHardLink] & 
+       dsFList::HardLink::FlMD5) == 0)
+      return _error->Error("The file list must contain MD5 hashes");
+   
+   string Dir;
+   while (List.Step(IO) == true)
+   {
+      if (List.Tag == dsFList::tDirStart)
+      {
+        Dir = List.Dir.Name;
+        continue;
+      }
+      
+      PrintMD5(List,Dir.c_str());
+      
+      if (List.Tag == dsFList::tTrailer)
+        break;
+   }   
+   return true;
+}
+                                                                       /*}}}*/
+// DoVerify - Verify the local tree against a file list                        /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool DoVerify(CommandLine &CmdL)
+{
+   if (CmdL.FileList[1] == 0)
+      return _error->Error("You must specify a file name");
+   
+   // Open the file
+   dsMMapIO IO(CmdL.FileList[1]);
+   if (_error->PendingError() == true)
+      return false;
+   
+   /* Set the hashing type, we can either do a full verify or only a date
+      check verify */
+   Compare Comp;
+   if (_config->FindB("FileList::MD5-Hashes",false) == true)
+      Comp.HashLevel = dsDirCompare::Md5Always;
+   else
+      Comp.HashLevel = dsDirCompare::Md5Date;
+   
+   // Scan the file list
+   if (Comp.Process(".",IO) == false)
+      return false;
+   Comp.Prog.Done();
+   
+   // Report stats
+   Comp.Prog.Stats((IO.Header.Flags[dsFList::tNormalFile] & dsFList::NormalFile::FlMD5) != 0 ||
+                  (IO.Header.Flags[dsFList::tHardLink] & dsFList::HardLink::FlMD5) != 0);
+   
+   return true;
+}
+                                                                       /*}}}*/
+// SigWinch - Window size change signal handler                                /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void SigWinch(int)
+{
+   // Riped from GNU ls
+#ifdef TIOCGWINSZ
+   struct winsize ws;
+  
+   if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col >= 5)
+      ScreenWidth = ws.ws_col - 1;
+   if (ScreenWidth > 250)
+      ScreenWidth = 250;
+#endif
+}
+                                                                       /*}}}*/
+// ShowHelp - Show the help screen                                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool ShowHelp(CommandLine &CmdL)
+{
+   cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
+      " compiled on " << __DATE__ << "  " << __TIME__ << endl;
+   
+   cout << 
+      "Usage: dsync-flist [options] command [file]\n"
+      "\n"
+      "dsync-flist is a tool for manipulating dsync binary file lists.\n"
+      "It can generate the lists and check them against a tree.\n"
+      "\n"
+      "Commands:\n"
+      "   generate - Build a file list\n"
+      "   help - This help text\n"
+      "   dump - Display the contents of the list\n"
+      "   md5sums - Print out 'indices' file, suitable for use with md5sum\n"
+      "   md5cache - Print out md5sums of the files given on stdin\n"
+      "   link-dups - Look for duplicate files\n"
+      "   lookup - Display a single file record\n"
+      "   verify - Compare the file list against the local directory\n"
+      "\n"   
+      "Options:\n"
+      "  -h  This help text.\n"
+      "  -q  Loggable output - no progress indicator\n"
+      "  -qq No output except for errors\n"
+      "  -i=? Include pattern\n"
+      "  -e=? Exclude pattern\n"
+      "  -c=? Read this configuration file\n"
+      "  -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp\n"
+      "See the dsync-flist(1) and dsync.conf(5) manual\n"
+      "pages for more information." << endl;
+   return 100;
+}
+                                                                       /*}}}*/
+
+int main(int argc, const char *argv[])
+{
+   CommandLine::Args Args[] = {
+      {'h',"help","help",0},
+      {'q',"quiet","quiet",CommandLine::IntLevel},
+      {'q',"silent","quiet",CommandLine::IntLevel},
+      {'i',"include","FileList::Filter:: + ",CommandLine::HasArg},
+      {'e',"exclude","FileList::Filter:: - ",CommandLine::HasArg},
+      {'n',"no-act","noact",0},
+      {'v',"verbose","verbose",CommandLine::IntLevel},
+      {0,"delete","delete",0},
+      {0,"prefer-include","FileList::Prefer-Filter:: + ",CommandLine::HasArg},
+      {0,"prefer-exclude","FileList::Prefer-Filter:: - ",CommandLine::HasArg},
+      {0,"pi","FileList::Prefer-Filter:: + ",CommandLine::HasArg},
+      {0,"pe","FileList::Prefer-Filter:: - ",CommandLine::HasArg},
+      {0,"clean-include","FList::Clean-Filter:: + ",CommandLine::HasArg},
+      {0,"clean-exclude","FList::Clean-Filter:: - ",CommandLine::HasArg},
+      {0,"ci","FList::Clean-Filter:: + ",CommandLine::HasArg},
+      {0,"ce","FList::Clean-Filter:: - ",CommandLine::HasArg},
+      {0,"rsync-include","FList::RSync-Filter:: + ",CommandLine::HasArg},
+      {0,"rsync-exclude","FList::RSync-Filter:: - ",CommandLine::HasArg},
+      {0,"ri","FList::RSync-Filter:: + ",CommandLine::HasArg},
+      {0,"re","FList::RSync-Filter:: - ",CommandLine::HasArg},
+      {0,"md5","FileList::MD5-Hashes",0},
+      {0,"rsync","FileList::RSync-Hashes",0},
+      {0,"rsync-min","FileList::MinRSyncSize",CommandLine::HasArg},
+      {0,"perm","FileList::Permissions",0},
+      {0,"owner","FileList::Ownership",0},
+      {0,"order","FileList::Order",CommandLine::HasArg},
+      {'c',"config-file",0,CommandLine::ConfigFile},
+      {'o',"option",0,CommandLine::ArbItem},
+      {0,0,0,0}};
+   CommandLine::Dispatch Cmds[] = {{"generate",&DoGenerate},
+                                   {"help",&ShowHelp},
+                                   {"dump",&DoDump},
+                                   {"link-dups",&DoMkHardLinks},
+                                   {"md5sums",&DoMD5Dump},
+                                   {"md5cache",&DoMD5Cache},
+                                   {"lookup",&DoLookup},
+                                   {"verify",&DoVerify},
+                                   {0,0}};
+   CommandLine CmdL(Args,_config);
+   if (CmdL.Parse(argc,argv) == false)
+   {
+      _error->DumpErrors();
+      return 100;
+   }
+   
+   // See if the help should be shown
+   if (_config->FindB("help") == true ||
+       CmdL.FileSize() == 0)
+      return ShowHelp(CmdL);   
+
+   // Setup the output streams
+/*   c0out.rdbuf(cout.rdbuf());
+   c1out.rdbuf(cout.rdbuf());
+   c2out.rdbuf(cout.rdbuf()); */
+   if (_config->FindI("quiet",0) > 0)
+      c0out.rdbuf(devnull.rdbuf());
+   if (_config->FindI("quiet",0) > 1)
+      c1out.rdbuf(devnull.rdbuf());
+
+   // Setup the signals
+   signal(SIGWINCH,SigWinch);
+   SigWinch(0);
+   
+   // Match the operation
+   CmdL.DispatchArg(Cmds);
+   
+   // Print any errors or warnings found during parsing
+   if (_error->empty() == false)
+   {
+      
+      bool Errors = _error->PendingError();
+      _error->DumpErrors();
+      return Errors == true?100:0;
+   }
+         
+   return 0; 
+}
diff --git a/tools/dsync-0.0/cmdline/dsync-flist.h b/tools/dsync-0.0/cmdline/dsync-flist.h
new file mode 100644 (file)
index 0000000..aebfd08
--- /dev/null
@@ -0,0 +1,206 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: dsync-flist.h,v 1.5 1999/12/26 06:59:00 jgg Exp $
+/* ######################################################################
+   
+   Some header declarations..
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_FLIST_H
+#define DSYNC_FLIST_H
+
+#ifdef __GNUG__
+#pragma interface "dsync-flist.h"
+#endif 
+
+#include <dsync/genfilelist.h>
+#include <dsync/fileutl.h>
+#include <dsync/filelistdb.h>
+#include <dsync/compare.h>
+
+#include <sys/time.h>
+#include <iostream>
+#include <fstream>
+using namespace std;
+
+extern ostream c0out;
+extern ostream c1out;
+extern ostream c2out;
+extern ofstream devnull;
+extern unsigned int ScreenWidth;
+
+class FdIO : public dsFList::IO
+{
+   FileFd Fd;
+   public:
+   
+   virtual bool Read(void *Buf,unsigned long Len) {return Fd.Read(Buf,Len);};
+   virtual bool Write(const void *Buf,unsigned long Len) {return Fd.Write(Buf,Len);};
+   virtual bool Seek(unsigned long Bytes) {return Fd.Seek(Bytes);};
+   virtual unsigned long Tell() {return Fd.Tell();};
+   
+   FdIO(string File,FileFd::OpenMode Mode) : Fd(File,Mode) {};
+};
+
+class Progress
+{
+   bool Quiet;   
+   
+   char LastLine[300];
+   char BlankLine[300];
+   
+   public:
+   
+   // Counters
+   unsigned long DirCount;
+   unsigned long FileCount;
+   unsigned long LinkCount;
+   unsigned long LastCount;
+   double Bytes;      
+   double CkSumBytes;
+   struct timeval StartTime;
+
+   double ElapsedTime();
+   void Done();
+   void Update(const char *Dir);
+   void Stats(bool Md5);
+   
+   inline void Hide() 
+   {
+      if (Quiet == false)
+        c0out << '\r' << BlankLine << '\r';
+   };
+   inline void Show()
+   {
+      if (Quiet == false)
+        c0out << LastLine << '\r' << flush;
+   };
+
+   Progress();
+   ~Progress() {Done();};
+};
+
+class ListGenerator : public dsGenFileList
+{
+   protected:
+   bool Act;
+   bool Verbose;
+   unsigned long MinRSyncSize;
+   unsigned int StripDepth;
+   
+   virtual int Visit(const char *Directory,const char *File,
+                    struct stat const &Stat);
+   virtual bool EmitMD5(const char *Dir,const char *File,
+                       struct stat const &St,unsigned char MD5[16],
+                       unsigned int Tag,unsigned int Flag);
+   virtual bool NeedsRSync(const char *Dir,const char *File,
+                          dsFList::NormalFile &F);
+   
+   public:
+
+   // Md5 Cache
+   dsFileListDB *DB;
+   dsMMapIO *DBIO;   
+   Progress Prog;
+   
+   dsFileFilter RemoveFilter;
+   dsFileFilter RSyncFilter;
+   
+   ListGenerator();
+   ~ListGenerator();
+};
+
+class Compare : public dsDirCorrect
+{
+   protected:
+   
+   bool Verbose;
+   bool Act;
+   bool DoDelete;
+   
+   virtual bool Visit(dsFList &List,string Dir);
+   void PrintPath(ostream &out,string Dir,string Name);
+   
+   // Display status information
+   virtual bool GetNew(dsFList &List,string Dir) 
+   {
+      Prog.Hide();
+      c1out << "N ";
+      PrintPath(c1out,Dir,List.Entity->Name);
+      Prog.Show();
+      return !Act || dsDirCorrect::GetNew(List,Dir);
+   };
+   virtual bool Delete(string Dir,const char *Name,bool Now = false) 
+   {
+      Prog.Hide();
+      c1out << "D ";
+      PrintPath(c1out,Dir,Name);
+      Prog.Show();
+      return !Act || !DoDelete || dsDirCorrect::Delete(Dir,Name);
+   };
+   virtual bool GetChanged(dsFList &List,string Dir)
+   {
+      Prog.Hide();
+      c1out << "C ";
+      PrintPath(c1out,Dir,List.Entity->Name);
+      Prog.Show();
+      return !Act || dsDirCorrect::GetChanged(List,Dir);
+   };
+   virtual bool SetTime(dsFList &List,string Dir)
+   {
+      if (Verbose == false)
+        return !Act || dsDirCorrect::SetTime(List,Dir);
+      
+      Prog.Hide();
+      c1out << "T ";
+      PrintPath(c1out,Dir,List.Entity->Name);
+      Prog.Show();
+      return !Act || dsDirCorrect::SetTime(List,Dir);
+   };
+   virtual bool SetPerm(dsFList &List,string Dir)
+   {
+      if (Verbose == false)
+        return !Act || dsDirCorrect::SetPerm(List,Dir);
+      Prog.Hide();
+      c1out << "P ";
+      PrintPath(c1out,Dir,List.Entity->Name);
+      Prog.Show();
+      return !Act || dsDirCorrect::SetPerm(List,Dir);
+   };
+   virtual bool SetOwners(dsFList &List,string Dir)
+   {
+      if (Verbose == false)
+        return !Act || dsDirCorrect::SetOwners(List,Dir);
+      Prog.Hide();
+      c1out << "O ";
+      PrintPath(c1out,Dir,List.Entity->Name);
+      Prog.Show();
+      return !Act || dsDirCorrect::SetOwners(List,Dir);
+   };
+   virtual bool CheckHash(dsFList &List,string Dir,unsigned char MD5[16])
+   {
+      Prog.CkSumBytes += List.File->Size;
+      
+      if (Verbose == true)
+      {
+        Prog.Hide();
+        c1out << "H ";
+        PrintPath(c1out,Dir,List.Entity->Name);
+        Prog.Show();
+      }      
+      return dsDirCompare::CheckHash(List,Dir,MD5);
+   }
+      
+   public:
+
+   Progress Prog;
+   
+   Compare();
+};
+
+// Path utilities
+bool SimplifyPath(char *Buffer);
+bool ResolveLink(char *Buffer,unsigned long Max);
+
+#endif
diff --git a/tools/dsync-0.0/cmdline/makefile b/tools/dsync-0.0/cmdline/makefile
new file mode 100644 (file)
index 0000000..b07669c
--- /dev/null
@@ -0,0 +1,18 @@
+# -*- make -*-
+BASE=..
+SUBDIR=cmdline
+
+# Bring in the default rules
+include ../buildlib/defaults.mak
+
+# Program to test the File Filter
+PROGRAM=dsync-flist
+SLIBS = -ldsync
+SOURCE = dsync-flist.cc path-utils.cc
+include $(PROGRAM_H)
+
+# Program to test the File Filter
+PROGRAM=dsync-cdimage
+SLIBS = -ldsync
+SOURCE = dsync-cdimage.cc
+include $(PROGRAM_H)
diff --git a/tools/dsync-0.0/cmdline/path-utils.cc b/tools/dsync-0.0/cmdline/path-utils.cc
new file mode 100644 (file)
index 0000000..7944dc6
--- /dev/null
@@ -0,0 +1,227 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: path-utils.cc,v 1.2 1999/03/22 02:52:46 jgg Exp $
+/* ######################################################################
+
+   Misc utility functions for dsync-flist to make use of.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#include "dsync-flist.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <dsync/error.h>
+
+// SimplifyPath - Short function to remove relative path components    /*{{{*/
+// ---------------------------------------------------------------------
+/* This short function removes relative path components such as ./ and ../
+   from the path and removes double // as well. It works by seperating
+   the path into a list of components and then removing any un-needed
+   compoments */
+bool SimplifyPath(char *Buffer)
+{
+   // Create a list of path compoments
+   char *Pos[100];
+   unsigned CurPos = 0;
+   Pos[CurPos] = Buffer;
+   CurPos++;   
+   for (char *I = Buffer; *I != 0;)
+   {
+      if (*I == '/')
+      {
+        *I = 0;
+        I++;
+        Pos[CurPos] = I;
+        CurPos++;
+      }
+      else
+        I++;
+   }
+   
+   // Strip //, ./ and ../
+   for (unsigned I = 0; I != CurPos; I++)
+   {
+      if (Pos[I] == 0)
+        continue;
+      
+      // Double slash
+      if (Pos[I][0] == 0)
+      {
+        if (I != 0)
+           Pos[I] = 0;
+        continue;
+      }
+      
+      // Dot slash
+      if (Pos[I][0] == '.' && Pos[I][1] == 0)
+      {
+        Pos[I] = 0;
+        continue;
+      }
+      
+      // Dot dot slash
+      if (Pos[I][0] == '.' && Pos[I][1] == '.' && Pos[I][2] == 0)
+      {
+        Pos[I] = 0;
+        unsigned J = I;
+        for (; Pos[J] == 0 && J != 0; J--);
+        if (Pos[J] == 0)
+           return _error->Error("Invalid path, too many ../s");
+        Pos[J] = 0;     
+        continue;
+      }
+   }  
+
+   // Recombine the path into full path
+   for (unsigned I = 0; I != CurPos; I++)
+   {
+      if (Pos[I] == 0)
+        continue;
+      memmove(Buffer,Pos[I],strlen(Pos[I]));
+      Buffer += strlen(Pos[I]);
+      
+      if (I + 1 != CurPos)
+        *Buffer++ = '/';
+   }                   
+   *Buffer = 0;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// ResolveLink - Resolve a file into an unsymlinked path               /*{{{*/
+// ---------------------------------------------------------------------
+/* The returned path is a path that accesses the same file without 
+   traversing a symlink, the memory buffer used should be twice as large
+   as the largest path. It uses an LRU cache of past lookups to speed things
+   up, just don't change directores :> */
+struct Cache
+{
+   string Dir;
+   string Trans;
+   unsigned long Age;
+};
+static Cache DirCache[400];
+static unsigned long CacheAge = 0;
+bool ResolveLink(char *Buffer,unsigned long Max)
+{
+   if (Buffer[0] == 0 || (Buffer[0] == '/' && Buffer[1] == 0))
+      return true;
+
+   // Lookup in the cache
+   Cache *Entry = 0;
+   for (int I = 0; I != 400; I++)
+   {
+      // Store an empty entry
+      if (DirCache[I].Dir.empty() == true)
+      {
+        Entry = &DirCache[I];
+        Entry->Age = 0;
+        continue;
+      }
+      
+      // Store the LRU entry
+      if (Entry != 0 && Entry->Age > DirCache[I].Age)
+        Entry = &DirCache[I];
+      
+      if (DirCache[I].Dir != Buffer || DirCache[I].Trans.empty() == true)
+        continue;
+      strcpy(Buffer,DirCache[I].Trans.c_str());
+      DirCache[I].Age = CacheAge++;
+      return true;
+   }
+   
+   // Prepare the cache for our new entry
+   if (Entry != 0 && Buffer[strlen(Buffer) - 1] == '/')
+   {
+      Entry->Age = CacheAge++;
+      Entry->Dir = Buffer;
+   }   
+   else
+      Entry = 0;
+
+   // Resolve any symlinks
+   unsigned Counter = 0;
+   while (1)
+   {
+      Counter++;
+      if (Counter > 50)
+        return _error->Error("Exceeded allowed symlink depth");
+      
+      // Strip off the final component name
+      char *I = Buffer + strlen(Buffer);
+      for (; I != Buffer && (*I == '/' || *I == 0); I--);
+      for (; I != Buffer && *I != '/'; I--);
+      if (I != Buffer)
+        I++;
+
+      if (strlen(I) == 0)
+        break;
+      
+
+      /* We need to remove the final slash in the directory component for
+         readlink to work right */
+      char *End = 0;
+      if (I[strlen(I) - 1] == '/')
+      {
+        End = I + strlen(I) - 1;
+        *End = 0;
+      }
+      
+      int Res = readlink(Buffer,I,Max - (I - Buffer) - 2);
+            
+      // If it is a link then read the link dest over the final component
+      if (Res > 0)
+      {
+        I[Res] = 0;
+        
+        // Absolute path..
+        if (*I == '/')
+           memmove(Buffer,I,strlen(I)+1);
+
+        // Put the slash back.. 
+        if (End != 0)
+        {       
+           I[Res] = '/';
+           I[Res + 1] = 0;
+        }
+        
+        if (SimplifyPath(Buffer) == false)
+           return false;        
+      }
+      else
+      {
+        // Put the slash back.. 
+        if (End != 0)
+           *End = '/';  
+        break;
+      }
+      
+   }
+   
+   /* Here we are abusive and move the current path component to the end 
+      of the buffer to advoid allocating space */
+   char *I = Buffer + strlen(Buffer);
+   for (; I != Buffer && (*I == '/' || *I == 0); I--);
+   for (; I != Buffer && *I != '/'; I--);
+   if (I != Buffer)
+      I++;
+   unsigned Len = strlen(I) + 1;
+   char *End = Buffer + Max - Len;
+   memmove(End,I,Len);
+   *I = 0;
+
+   
+   // Recurse to deal with any links in the files path
+   if (ResolveLink(Buffer,Max - Len) == false)
+      return false;
+   I = Buffer + strlen(Buffer);
+   memmove(I,End,Len);
+
+   // Store in the cache
+   if (Entry != 0)
+      Entry->Trans = Buffer;
+   
+   return true;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/configure b/tools/dsync-0.0/configure
new file mode 100755 (executable)
index 0000000..38db885
--- /dev/null
@@ -0,0 +1,7196 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.61.
+#
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+# 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## --------------------- ##
+## M4sh Initialization.  ##
+## --------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in
+  *posix*) set -o posix ;;
+esac
+
+fi
+
+
+
+
+# PATH needs CR
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+    PATH_SEPARATOR=';'
+  else
+    PATH_SEPARATOR=:
+  fi
+  rm -f conf$$.sh
+fi
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  as_unset=unset
+else
+  as_unset=false
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+as_nl='
+'
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+case $0 in
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  { (exit 1); exit 1; }
+fi
+
+# Work around bugs in pre-3.0 UWIN ksh.
+for as_var in ENV MAIL MAILPATH
+do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+  LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+  LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+  LC_TELEPHONE LC_TIME
+do
+  if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+    eval $as_var=C; export $as_var
+  else
+    ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
+  fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# CDPATH.
+$as_unset CDPATH
+
+
+if test "x$CONFIG_SHELL" = x; then
+  if (eval ":") 2>/dev/null; then
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+
+  if test $as_have_required = yes &&    (eval ":
+(as_func_return () {
+  (exit \$1)
+}
+as_func_success () {
+  as_func_return 0
+}
+as_func_failure () {
+  as_func_return 1
+}
+as_func_ret_success () {
+  return 0
+}
+as_func_ret_failure () {
+  return 1
+}
+
+exitcode=0
+if as_func_success; then
+  :
+else
+  exitcode=1
+  echo as_func_success failed.
+fi
+
+if as_func_failure; then
+  exitcode=1
+  echo as_func_failure succeeded.
+fi
+
+if as_func_ret_success; then
+  :
+else
+  exitcode=1
+  echo as_func_ret_success failed.
+fi
+
+if as_func_ret_failure; then
+  exitcode=1
+  echo as_func_ret_failure succeeded.
+fi
+
+if ( set x; as_func_ret_success y && test x = \"\$1\" ); then
+  :
+else
+  exitcode=1
+  echo positional parameters were not saved.
+fi
+
+test \$exitcode = 0) || { (exit 1); exit 1; }
+
+(
+  as_lineno_1=\$LINENO
+  as_lineno_2=\$LINENO
+  test \"x\$as_lineno_1\" != \"x\$as_lineno_2\" &&
+  test \"x\`expr \$as_lineno_1 + 1\`\" = \"x\$as_lineno_2\") || { (exit 1); exit 1; }
+") 2> /dev/null; then
+  :
+else
+  as_candidate_shells=
+    as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  case $as_dir in
+        /*)
+          for as_base in sh bash ksh sh5; do
+            as_candidate_shells="$as_candidate_shells $as_dir/$as_base"
+          done;;
+       esac
+done
+IFS=$as_save_IFS
+
+
+      for as_shell in $as_candidate_shells $SHELL; do
+        # Try only shells that exist, to save several forks.
+        if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+               { ("$as_shell") 2> /dev/null <<\_ASEOF
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in
+  *posix*) set -o posix ;;
+esac
+
+fi
+
+
+:
+_ASEOF
+}; then
+  CONFIG_SHELL=$as_shell
+              as_have_required=yes
+              if { "$as_shell" 2> /dev/null <<\_ASEOF
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in
+  *posix*) set -o posix ;;
+esac
+
+fi
+
+
+:
+(as_func_return () {
+  (exit $1)
+}
+as_func_success () {
+  as_func_return 0
+}
+as_func_failure () {
+  as_func_return 1
+}
+as_func_ret_success () {
+  return 0
+}
+as_func_ret_failure () {
+  return 1
+}
+
+exitcode=0
+if as_func_success; then
+  :
+else
+  exitcode=1
+  echo as_func_success failed.
+fi
+
+if as_func_failure; then
+  exitcode=1
+  echo as_func_failure succeeded.
+fi
+
+if as_func_ret_success; then
+  :
+else
+  exitcode=1
+  echo as_func_ret_success failed.
+fi
+
+if as_func_ret_failure; then
+  exitcode=1
+  echo as_func_ret_failure succeeded.
+fi
+
+if ( set x; as_func_ret_success y && test x = "$1" ); then
+  :
+else
+  exitcode=1
+  echo positional parameters were not saved.
+fi
+
+test $exitcode = 0) || { (exit 1); exit 1; }
+
+(
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2") || { (exit 1); exit 1; }
+
+_ASEOF
+}; then
+  break
+fi
+
+fi
+
+      done
+
+      if test "x$CONFIG_SHELL" != x; then
+  for as_var in BASH_ENV ENV
+        do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
+        done
+        export CONFIG_SHELL
+        exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"}
+fi
+
+
+    if test $as_have_required = no; then
+  echo This script requires a shell more modern than all the
+      echo shells that I found on your system.  Please install a
+      echo modern shell, or manually run the script under such a
+      echo shell if you do have one.
+      { (exit 1); exit 1; }
+fi
+
+
+fi
+
+fi
+
+
+
+(eval "as_func_return () {
+  (exit \$1)
+}
+as_func_success () {
+  as_func_return 0
+}
+as_func_failure () {
+  as_func_return 1
+}
+as_func_ret_success () {
+  return 0
+}
+as_func_ret_failure () {
+  return 1
+}
+
+exitcode=0
+if as_func_success; then
+  :
+else
+  exitcode=1
+  echo as_func_success failed.
+fi
+
+if as_func_failure; then
+  exitcode=1
+  echo as_func_failure succeeded.
+fi
+
+if as_func_ret_success; then
+  :
+else
+  exitcode=1
+  echo as_func_ret_success failed.
+fi
+
+if as_func_ret_failure; then
+  exitcode=1
+  echo as_func_ret_failure succeeded.
+fi
+
+if ( set x; as_func_ret_success y && test x = \"\$1\" ); then
+  :
+else
+  exitcode=1
+  echo positional parameters were not saved.
+fi
+
+test \$exitcode = 0") || {
+  echo No shell found that supports shell functions.
+  echo Please tell autoconf@gnu.org about your system,
+  echo including any error possibly output before this
+  echo message
+}
+
+
+
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || {
+
+  # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+  # uniformly replaced by the line number.  The first 'sed' inserts a
+  # line-number line after each line using $LINENO; the second 'sed'
+  # does the real work.  The second script uses 'N' to pair each
+  # line-number line with the line containing $LINENO, and appends
+  # trailing '-' during substitution so that $LINENO is not a special
+  # case at line end.
+  # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+  # scripts with optimization help from Paolo Bonzini.  Blame Lee
+  # E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2
+   { (exit 1); exit 1; }; }
+
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in
+-n*)
+  case `echo 'x\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  *)   ECHO_C='\c';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir
+fi
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+  as_ln_s='ln -s'
+  # ... but there are two gotchas:
+  # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+  # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+  # In both cases, we have to default to `cp -p'.
+  ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+    as_ln_s='cp -p'
+elif ln conf$$.file conf$$ 2>/dev/null; then
+  as_ln_s=ln
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p=:
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+        test -d "$1/.";
+      else
+       case $1 in
+        -*)set "./$1";;
+       esac;
+       case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in
+       ???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+
+exec 7<&0 </dev/null 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+# Identity of this package.
+PACKAGE_NAME=
+PACKAGE_TARNAME=
+PACKAGE_VERSION=
+PACKAGE_STRING=
+PACKAGE_BUGREPORT=
+
+ac_unique_file="configure.in"
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='SHELL
+PATH_SEPARATOR
+PACKAGE_NAME
+PACKAGE_TARNAME
+PACKAGE_VERSION
+PACKAGE_STRING
+PACKAGE_BUGREPORT
+exec_prefix
+prefix
+program_transform_name
+bindir
+sbindir
+libexecdir
+datarootdir
+datadir
+sysconfdir
+sharedstatedir
+localstatedir
+includedir
+oldincludedir
+docdir
+infodir
+htmldir
+dvidir
+pdfdir
+psdir
+libdir
+localedir
+mandir
+DEFS
+ECHO_C
+ECHO_N
+ECHO_T
+LIBS
+build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+CPPFLAGS
+ac_ct_CC
+EXEEXT
+OBJEXT
+build
+build_cpu
+build_vendor
+build_os
+host
+host_cpu
+host_vendor
+host_os
+CXX
+CXXFLAGS
+ac_ct_CXX
+AR
+PTHREADLIB
+CXXCPP
+GREP
+EGREP
+HAVE_C9X
+DEBIANDOC_HTML
+DEBIANDOC_TEXT
+YODL_MAN
+LIBOBJS
+LTLIBOBJS'
+ac_subst_files=''
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CXX
+CXXFLAGS
+CCC
+CXXCPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *)   ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+   { (exit 1); exit 1; }; }
+    ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'`
+    eval enable_$ac_feature=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+   { (exit 1); exit 1; }; }
+    ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'`
+    eval enable_$ac_feature=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid package name: $ac_package" >&2
+   { (exit 1); exit 1; }; }
+    ac_package=`echo $ac_package | sed 's/[-.]/_/g'`
+    eval with_$ac_package=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid package name: $ac_package" >&2
+   { (exit 1); exit 1; }; }
+    ac_package=`echo $ac_package | sed 's/[-.]/_/g'`
+    eval with_$ac_package=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) { echo "$as_me: error: unrecognized option: $ac_option
+Try \`$0 --help' for more information." >&2
+   { (exit 1); exit 1; }; }
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid variable name: $ac_envvar" >&2
+   { (exit 1); exit 1; }; }
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  { echo "$as_me: error: missing argument to $ac_option" >&2
+   { (exit 1); exit 1; }; }
+fi
+
+# Be sure to have absolute directory names.
+for ac_var in  exec_prefix prefix bindir sbindir libexecdir datarootdir \
+               datadir sysconfdir sharedstatedir localstatedir includedir \
+               oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+               libdir localedir mandir
+do
+  eval ac_val=\$$ac_var
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+   { (exit 1); exit 1; }; }
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+    echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
+    If a cross compiler is detected then cross compile mode will be used." >&2
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  { echo "$as_me: error: Working directory cannot be determined" >&2
+   { (exit 1); exit 1; }; }
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  { echo "$as_me: error: pwd does not report name of working directory" >&2
+   { (exit 1); exit 1; }; }
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$0" ||
+$as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$0" : 'X\(//\)[^/]' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+echo X"$0" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2
+   { (exit 1); exit 1; }; }
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+       cd "$srcdir" && test -r "./$ac_unique_file" || { echo "$as_me: error: $ac_msg" >&2
+   { (exit 1); exit 1; }; }
+       pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures this package to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                         [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                         [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR           user executables [EPREFIX/bin]
+  --sbindir=DIR          system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR       program executables [EPREFIX/libexec]
+  --sysconfdir=DIR       read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR   modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR    modifiable single-machine data [PREFIX/var]
+  --libdir=DIR           object code libraries [EPREFIX/lib]
+  --includedir=DIR       C header files [PREFIX/include]
+  --oldincludedir=DIR    C header files for non-gcc [/usr/include]
+  --datarootdir=DIR      read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR          read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR          info documentation [DATAROOTDIR/info]
+  --localedir=DIR        locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR           man documentation [DATAROOTDIR/man]
+  --docdir=DIR           documentation root [DATAROOTDIR/doc/PACKAGE]
+  --htmldir=DIR          html documentation [DOCDIR]
+  --dvidir=DIR           dvi documentation [DOCDIR]
+  --pdfdir=DIR           pdf documentation [DOCDIR]
+  --psdir=DIR            ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+
+System types:
+  --build=BUILD     configure for building on BUILD [guessed]
+  --host=HOST       cross-compile to build programs to run on HOST [BUILD]
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+
+  cat <<\_ACEOF
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    C/C++/Objective C preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  CXX         C++ compiler command
+  CXXFLAGS    C++ compiler flags
+  CXXCPP      C++ preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" || continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+configure
+generated by GNU Autoconf 2.61
+
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by $as_me, which was
+generated by GNU Autoconf 2.61.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  echo "PATH: $as_dir"
+done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;;
+    2)
+      ac_configure_args1="$ac_configure_args1 '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+       ac_must_keep_next=false # Got value, back to normal.
+      else
+       case $ac_arg in
+         *=* | --config-cache | -C | -disable-* | --disable-* \
+         | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+         | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+         | -with-* | --with-* | -without-* | --without-* | --x)
+           case "$ac_configure_args0 " in
+             "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+           esac
+           ;;
+         -* ) ac_must_keep_next=true ;;
+       esac
+      fi
+      ac_configure_args="$ac_configure_args '$ac_arg'"
+      ;;
+    esac
+  done
+done
+$as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; }
+$as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; }
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    cat <<\_ASBOX
+## ---------------- ##
+## Cache variables. ##
+## ---------------- ##
+_ASBOX
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5
+echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      *) $as_unset $ac_var ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+       "s/'\''/'\''\\\\'\'''\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    cat <<\_ASBOX
+## ----------------- ##
+## Output variables. ##
+## ----------------- ##
+_ASBOX
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      cat <<\_ASBOX
+## ------------------- ##
+## File substitutions. ##
+## ------------------- ##
+_ASBOX
+      echo
+      for ac_var in $ac_subst_files
+      do
+       eval ac_val=\$$ac_var
+       case $ac_val in
+       *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+       esac
+       echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      cat <<\_ASBOX
+## ----------- ##
+## confdefs.h. ##
+## ----------- ##
+_ASBOX
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      echo "$as_me: caught signal $ac_signal"
+    echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer explicitly selected file to automatically selected ones.
+if test -n "$CONFIG_SITE"; then
+  set x "$CONFIG_SITE"
+elif test "x$prefix" != xNONE; then
+  set x "$prefix/share/config.site" "$prefix/etc/config.site"
+else
+  set x "$ac_default_prefix/share/config.site" \
+       "$ac_default_prefix/etc/config.site"
+fi
+shift
+for ac_site_file
+do
+  if test -r "$ac_site_file"; then
+    { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5
+echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file"
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special
+  # files actually), so we avoid doing that.
+  if test -f "$cache_file"; then
+    { echo "$as_me:$LINENO: loading cache $cache_file" >&5
+echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { echo "$as_me:$LINENO: creating cache $cache_file" >&5
+echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+       { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5
+echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+       { echo "$as_me:$LINENO:   former value:  $ac_old_val" >&5
+echo "$as_me:   former value:  $ac_old_val" >&2;}
+       { echo "$as_me:$LINENO:   current value: $ac_new_val" >&5
+echo "$as_me:   current value: $ac_new_val" >&2;}
+       ac_cache_corrupted=:
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5
+echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5
+echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_aux_dir=
+for ac_dir in buildlib "$srcdir"/buildlib; do
+  if test -f "$ac_dir/install-sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f "$ac_dir/install.sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  elif test -f "$ac_dir/shtool"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/shtool install -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  { { echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in buildlib \"$srcdir\"/buildlib" >&5
+echo "$as_me: error: cannot find install-sh or install.sh in buildlib \"$srcdir\"/buildlib" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+
+
+ac_config_headers="$ac_config_headers include/config.h:buildlib/config.h.in"
+
+
+cat >>confdefs.h <<_ACEOF
+#define VERSION "0.0"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE "dsync"
+_ACEOF
+
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&5
+echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&5
+echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { echo "$as_me:$LINENO: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&5
+echo "$as_me: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+
+# Provide some information about the compiler.
+echo "$as_me:$LINENO: checking for C compiler version" >&5
+ac_compiler=`set X $ac_compile; echo $2`
+{ (ac_try="$ac_compiler --version >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compiler --version >&5") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+{ (ac_try="$ac_compiler -v >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compiler -v >&5") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+{ (ac_try="$ac_compiler -V >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compiler -V >&5") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ echo "$as_me:$LINENO: checking for C compiler default output file name" >&5
+echo $ECHO_N "checking for C compiler default output file name... $ECHO_C" >&6; }
+ac_link_default=`echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+#
+# List of possible output files, starting from the most likely.
+# The algorithm is not robust to junk in `.', hence go to wildcards (a.*)
+# only as a last resort.  b.out is created by i960 compilers.
+ac_files='a_out.exe a.exe conftest.exe a.out conftest a.* conftest.* b.out'
+#
+# The IRIX 6 linker writes into existing files which may not be
+# executable, retaining their permissions.  Remove them first so a
+# subsequent execution test works.
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { (ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; then
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj )
+       ;;
+    [ab].out )
+       # We found the default executable, but exeext='' is most
+       # certainly right.
+       break;;
+    *.* )
+        if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+       then :; else
+          ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+       fi
+       # We set ac_cv_exeext here because the later test for it is not
+       # safe: cross compilers may not add the suffix if given an `-o'
+       # argument, so we may need to know it at that point already.
+       # Even if this section looks crufty: it has the advantage of
+       # actually working.
+       break;;
+    * )
+       break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+
+{ echo "$as_me:$LINENO: result: $ac_file" >&5
+echo "${ECHO_T}$ac_file" >&6; }
+if test -z "$ac_file"; then
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: C compiler cannot create executables
+See \`config.log' for more details." >&5
+echo "$as_me: error: C compiler cannot create executables
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+fi
+
+ac_exeext=$ac_cv_exeext
+
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ echo "$as_me:$LINENO: checking whether the C compiler works" >&5
+echo $ECHO_N "checking whether the C compiler works... $ECHO_C" >&6; }
+# FIXME: These cross compiler hacks should be removed for Autoconf 3.0
+# If not cross compiling, check that we can run a simple program.
+if test "$cross_compiling" != yes; then
+  if { ac_try='./$ac_file'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+       cross_compiling=yes
+    else
+       { { echo "$as_me:$LINENO: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+    fi
+  fi
+fi
+{ echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6; }
+
+rm -f a.out a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ echo "$as_me:$LINENO: checking whether we are cross compiling" >&5
+echo $ECHO_N "checking whether we are cross compiling... $ECHO_C" >&6; }
+{ echo "$as_me:$LINENO: result: $cross_compiling" >&5
+echo "${ECHO_T}$cross_compiling" >&6; }
+
+{ echo "$as_me:$LINENO: checking for suffix of executables" >&5
+echo $ECHO_N "checking for suffix of executables... $ECHO_C" >&6; }
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; then
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+         break;;
+    * ) break;;
+  esac
+done
+else
+  { { echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest$ac_cv_exeext
+{ echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5
+echo "${ECHO_T}$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+{ echo "$as_me:$LINENO: checking for suffix of object files" >&5
+echo $ECHO_N "checking for suffix of object files... $ECHO_C" >&6; }
+if test "${ac_cv_objext+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; then
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_objext" >&5
+echo "${ECHO_T}$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ echo "$as_me:$LINENO: checking whether we are using the GNU C compiler" >&5
+echo $ECHO_N "checking whether we are using the GNU C compiler... $ECHO_C" >&6; }
+if test "${ac_cv_c_compiler_gnu+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_compiler_gnu=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_compiler_gnu=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_c_compiler_gnu" >&5
+echo "${ECHO_T}$ac_cv_c_compiler_gnu" >&6; }
+GCC=`test $ac_compiler_gnu = yes && echo yes`
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ echo "$as_me:$LINENO: checking whether $CC accepts -g" >&5
+echo $ECHO_N "checking whether $CC accepts -g... $ECHO_C" >&6; }
+if test "${ac_cv_prog_cc_g+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_prog_cc_g=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       CFLAGS=""
+      cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  :
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_c_werror_flag=$ac_save_c_werror_flag
+        CFLAGS="-g"
+        cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_prog_cc_g=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_prog_cc_g" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ echo "$as_me:$LINENO: checking for $CC option to accept ISO C89" >&5
+echo $ECHO_N "checking for $CC option to accept ISO C89... $ECHO_C" >&6; }
+if test "${ac_cv_prog_cc_c89+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+       -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_prog_cc_c89=$ac_arg
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { echo "$as_me:$LINENO: result: none needed" >&5
+echo "${ECHO_T}none needed" >&6; } ;;
+  xno)
+    { echo "$as_me:$LINENO: result: unsupported" >&5
+echo "${ECHO_T}unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { echo "$as_me:$LINENO: result: $ac_cv_prog_cc_c89" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+{ echo "$as_me:$LINENO: checking for library containing strerror" >&5
+echo $ECHO_N "checking for library containing strerror... $ECHO_C" >&6; }
+if test "${ac_cv_search_strerror+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_func_search_save_LIBS=$LIBS
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char strerror ();
+int
+main ()
+{
+return strerror ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' cposix; do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  ac_cv_search_strerror=$ac_res
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext
+  if test "${ac_cv_search_strerror+set}" = set; then
+  break
+fi
+done
+if test "${ac_cv_search_strerror+set}" = set; then
+  :
+else
+  ac_cv_search_strerror=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_search_strerror" >&5
+echo "${ECHO_T}$ac_cv_search_strerror" >&6; }
+ac_res=$ac_cv_search_strerror
+if test "$ac_res" != no; then
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+
+# Make sure we can run config.sub.
+$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
+  { { echo "$as_me:$LINENO: error: cannot run $SHELL $ac_aux_dir/config.sub" >&5
+echo "$as_me: error: cannot run $SHELL $ac_aux_dir/config.sub" >&2;}
+   { (exit 1); exit 1; }; }
+
+{ echo "$as_me:$LINENO: checking build system type" >&5
+echo $ECHO_N "checking build system type... $ECHO_C" >&6; }
+if test "${ac_cv_build+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_build_alias=$build_alias
+test "x$ac_build_alias" = x &&
+  ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
+test "x$ac_build_alias" = x &&
+  { { echo "$as_me:$LINENO: error: cannot guess build type; you must specify one" >&5
+echo "$as_me: error: cannot guess build type; you must specify one" >&2;}
+   { (exit 1); exit 1; }; }
+ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
+  { { echo "$as_me:$LINENO: error: $SHELL $ac_aux_dir/config.sub $ac_build_alias failed" >&5
+echo "$as_me: error: $SHELL $ac_aux_dir/config.sub $ac_build_alias failed" >&2;}
+   { (exit 1); exit 1; }; }
+
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_build" >&5
+echo "${ECHO_T}$ac_cv_build" >&6; }
+case $ac_cv_build in
+*-*-*) ;;
+*) { { echo "$as_me:$LINENO: error: invalid value of canonical build" >&5
+echo "$as_me: error: invalid value of canonical build" >&2;}
+   { (exit 1); exit 1; }; };;
+esac
+build=$ac_cv_build
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_build
+shift
+build_cpu=$1
+build_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+build_os=$*
+IFS=$ac_save_IFS
+case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
+
+
+{ echo "$as_me:$LINENO: checking host system type" >&5
+echo $ECHO_N "checking host system type... $ECHO_C" >&6; }
+if test "${ac_cv_host+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test "x$host_alias" = x; then
+  ac_cv_host=$ac_cv_build
+else
+  ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
+    { { echo "$as_me:$LINENO: error: $SHELL $ac_aux_dir/config.sub $host_alias failed" >&5
+echo "$as_me: error: $SHELL $ac_aux_dir/config.sub $host_alias failed" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_host" >&5
+echo "${ECHO_T}$ac_cv_host" >&6; }
+case $ac_cv_host in
+*-*-*) ;;
+*) { { echo "$as_me:$LINENO: error: invalid value of canonical host" >&5
+echo "$as_me: error: invalid value of canonical host" >&2;}
+   { (exit 1); exit 1; }; };;
+esac
+host=$ac_cv_host
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_host
+shift
+host_cpu=$1
+host_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+host_os=$*
+IFS=$ac_save_IFS
+case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
+
+
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+if test -z "$CXX"; then
+  if test -n "$CCC"; then
+    CXX=$CCC
+  else
+    if test -n "$ac_tool_prefix"; then
+  for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_CXX+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CXX"; then
+  ac_cv_prog_CXX="$CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CXX="$ac_tool_prefix$ac_prog"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+CXX=$ac_cv_prog_CXX
+if test -n "$CXX"; then
+  { echo "$as_me:$LINENO: result: $CXX" >&5
+echo "${ECHO_T}$CXX" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+    test -n "$CXX" && break
+  done
+fi
+if test -z "$CXX"; then
+  ac_ct_CXX=$CXX
+  for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_ac_ct_CXX+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$ac_ct_CXX"; then
+  ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CXX="$ac_prog"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CXX=$ac_cv_prog_ac_ct_CXX
+if test -n "$ac_ct_CXX"; then
+  { echo "$as_me:$LINENO: result: $ac_ct_CXX" >&5
+echo "${ECHO_T}$ac_ct_CXX" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CXX" && break
+done
+
+  if test "x$ac_ct_CXX" = x; then
+    CXX="g++"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&5
+echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&2;}
+ac_tool_warned=yes ;;
+esac
+    CXX=$ac_ct_CXX
+  fi
+fi
+
+  fi
+fi
+# Provide some information about the compiler.
+echo "$as_me:$LINENO: checking for C++ compiler version" >&5
+ac_compiler=`set X $ac_compile; echo $2`
+{ (ac_try="$ac_compiler --version >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compiler --version >&5") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+{ (ac_try="$ac_compiler -v >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compiler -v >&5") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+{ (ac_try="$ac_compiler -V >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compiler -V >&5") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+
+{ echo "$as_me:$LINENO: checking whether we are using the GNU C++ compiler" >&5
+echo $ECHO_N "checking whether we are using the GNU C++ compiler... $ECHO_C" >&6; }
+if test "${ac_cv_cxx_compiler_gnu+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_compiler_gnu=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_compiler_gnu=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_cxx_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_cxx_compiler_gnu" >&5
+echo "${ECHO_T}$ac_cv_cxx_compiler_gnu" >&6; }
+GXX=`test $ac_compiler_gnu = yes && echo yes`
+ac_test_CXXFLAGS=${CXXFLAGS+set}
+ac_save_CXXFLAGS=$CXXFLAGS
+{ echo "$as_me:$LINENO: checking whether $CXX accepts -g" >&5
+echo $ECHO_N "checking whether $CXX accepts -g... $ECHO_C" >&6; }
+if test "${ac_cv_prog_cxx_g+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_save_cxx_werror_flag=$ac_cxx_werror_flag
+   ac_cxx_werror_flag=yes
+   ac_cv_prog_cxx_g=no
+   CXXFLAGS="-g"
+   cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_prog_cxx_g=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       CXXFLAGS=""
+      cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  :
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+        CXXFLAGS="-g"
+        cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_prog_cxx_g=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_prog_cxx_g" >&5
+echo "${ECHO_T}$ac_cv_prog_cxx_g" >&6; }
+if test "$ac_test_CXXFLAGS" = set; then
+  CXXFLAGS=$ac_save_CXXFLAGS
+elif test $ac_cv_prog_cxx_g = yes; then
+  if test "$GXX" = yes; then
+    CXXFLAGS="-g -O2"
+  else
+    CXXFLAGS="-g"
+  fi
+else
+  if test "$GXX" = yes; then
+    CXXFLAGS="-O2"
+  else
+    CXXFLAGS=
+  fi
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ar; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_AR+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$AR"; then
+  ac_cv_prog_AR="$AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_AR="${ac_tool_prefix}ar"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+AR=$ac_cv_prog_AR
+if test -n "$AR"; then
+  { echo "$as_me:$LINENO: result: $AR" >&5
+echo "${ECHO_T}$AR" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_AR"; then
+  ac_ct_AR=$AR
+  # Extract the first word of "ar", so it can be a program name with args.
+set dummy ar; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_ac_ct_AR+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$ac_ct_AR"; then
+  ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_AR="ar"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_AR=$ac_cv_prog_ac_ct_AR
+if test -n "$ac_ct_AR"; then
+  { echo "$as_me:$LINENO: result: $ac_ct_AR" >&5
+echo "${ECHO_T}$ac_ct_AR" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+  if test "x$ac_ct_AR" = x; then
+    AR=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&5
+echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools
+whose name does not start with the host triplet.  If you think this
+configuration is useful to you, please write to autoconf@gnu.org." >&2;}
+ac_tool_warned=yes ;;
+esac
+    AR=$ac_ct_AR
+  fi
+else
+  AR="$ac_cv_prog_AR"
+fi
+
+
+if test "$AR" = ":"; then
+     { { echo "$as_me:$LINENO: error: failed: Sorry I could not find ar in the path" >&5
+echo "$as_me: error: failed: Sorry I could not find ar in the path" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+
+{ echo "$as_me:$LINENO: checking for pthread_create in -lpthread" >&5
+echo $ECHO_N "checking for pthread_create in -lpthread... $ECHO_C" >&6; }
+if test "${ac_cv_lib_pthread_pthread_create+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpthread  $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char pthread_create ();
+int
+main ()
+{
+return pthread_create ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  ac_cv_lib_pthread_pthread_create=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cv_lib_pthread_pthread_create=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_lib_pthread_pthread_create" >&5
+echo "${ECHO_T}$ac_cv_lib_pthread_pthread_create" >&6; }
+if test $ac_cv_lib_pthread_pthread_create = yes; then
+  cat >>confdefs.h <<\_ACEOF
+#define HAVE_PTHREAD 1
+_ACEOF
+ PTHREADLIB="-lpthread"
+fi
+
+
+
+{ echo "$as_me:$LINENO: checking system architecture" >&5
+echo $ECHO_N "checking system architecture... $ECHO_C" >&6; }
+archset="`awk '$1 == "'$host_cpu'" { print $2 }' $srcdir/buildlib/archtable`"
+if test "x$archset" = "x"; then
+  { { echo "$as_me:$LINENO: error: failed: use --host=" >&5
+echo "$as_me: error: failed: use --host=" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+{ echo "$as_me:$LINENO: result: $archset" >&5
+echo "${ECHO_T}$archset" >&6; }
+cat >>confdefs.h <<_ACEOF
+#define ARCHITECTURE "$archset"
+_ACEOF
+
+
+{ echo "$as_me:$LINENO: checking for C9x integer types" >&5
+echo $ECHO_N "checking for C9x integer types... $ECHO_C" >&6; }
+if test "${c9x_ints+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <inttypes.h>
+int
+main ()
+{
+uint8_t Foo1;uint16_t Foo2;uint32_t Foo3;uint64_t Foo
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  c9x_ints=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       c9x_ints=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $c9x_ints" >&5
+echo "${ECHO_T}$c9x_ints" >&6; }
+
+
+if archline="`sed -ne 's/^'$archset':[         ]\+\(.*\)/\1/gp' $srcdir/buildlib/sizetable`"; then
+
+   set $archline
+   if test "$1" = "little"; then
+      ac_cv_c_bigendian=no
+   else
+      ac_cv_c_bigendian=yes
+   fi
+   size_char=$2
+   size_int=$3
+   size_short=$4
+   size_long=$5
+fi
+
+if test "$cross_compiling" = "yes" -a "$archline" = ""; then
+  { { echo "$as_me:$LINENO: error: When cross compiling" >&5
+echo "$as_me: error: When cross compiling" >&2;}
+   { (exit architecture must be present in sizetable); exit architecture must be present in sizetable; }; }
+fi
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+{ echo "$as_me:$LINENO: checking how to run the C++ preprocessor" >&5
+echo $ECHO_N "checking how to run the C++ preprocessor... $ECHO_C" >&6; }
+if test -z "$CXXCPP"; then
+  if test "${ac_cv_prog_CXXCPP+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+      # Double quotes because CXXCPP needs to be expanded
+    for CXXCPP in "$CXX -E" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_cxx_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if { (ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null && {
+        test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       }; then
+  :
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Broken: fails on valid input.
+continue
+fi
+
+rm -f conftest.err conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if { (ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null && {
+        test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       }; then
+  # Broken: success on invalid input.
+continue
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+
+rm -f conftest.err conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then
+  break
+fi
+
+    done
+    ac_cv_prog_CXXCPP=$CXXCPP
+
+fi
+  CXXCPP=$ac_cv_prog_CXXCPP
+else
+  ac_cv_prog_CXXCPP=$CXXCPP
+fi
+{ echo "$as_me:$LINENO: result: $CXXCPP" >&5
+echo "${ECHO_T}$CXXCPP" >&6; }
+ac_preproc_ok=false
+for ac_cxx_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if { (ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null && {
+        test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       }; then
+  :
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Broken: fails on valid input.
+continue
+fi
+
+rm -f conftest.err conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if { (ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null && {
+        test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       }; then
+  # Broken: success on invalid input.
+continue
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+
+rm -f conftest.err conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then
+  :
+else
+  { { echo "$as_me:$LINENO: error: C++ preprocessor \"$CXXCPP\" fails sanity check
+See \`config.log' for more details." >&5
+echo "$as_me: error: C++ preprocessor \"$CXXCPP\" fails sanity check
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+{ echo "$as_me:$LINENO: checking for grep that handles long lines and -e" >&5
+echo $ECHO_N "checking for grep that handles long lines and -e... $ECHO_C" >&6; }
+if test "${ac_cv_path_GREP+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  # Extract the first word of "grep ggrep" to use in msg output
+if test -z "$GREP"; then
+set dummy grep ggrep; ac_prog_name=$2
+if test "${ac_cv_path_GREP+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_path_GREP_found=false
+# Loop through the user's path and test for each of PROGNAME-LIST
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_prog in grep ggrep; do
+  for ac_exec_ext in '' $ac_executable_extensions; do
+    ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+    { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue
+    # Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  echo $ECHO_N "0123456789$ECHO_C" >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    echo 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    ac_count=`expr $ac_count + 1`
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+
+    $ac_path_GREP_found && break 3
+  done
+done
+
+done
+IFS=$as_save_IFS
+
+
+fi
+
+GREP="$ac_cv_path_GREP"
+if test -z "$GREP"; then
+  { { echo "$as_me:$LINENO: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5
+echo "$as_me: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_path_GREP" >&5
+echo "${ECHO_T}$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ echo "$as_me:$LINENO: checking for egrep" >&5
+echo $ECHO_N "checking for egrep... $ECHO_C" >&6; }
+if test "${ac_cv_path_EGREP+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     # Extract the first word of "egrep" to use in msg output
+if test -z "$EGREP"; then
+set dummy egrep; ac_prog_name=$2
+if test "${ac_cv_path_EGREP+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_path_EGREP_found=false
+# Loop through the user's path and test for each of PROGNAME-LIST
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_prog in egrep; do
+  for ac_exec_ext in '' $ac_executable_extensions; do
+    ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+    { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue
+    # Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  echo $ECHO_N "0123456789$ECHO_C" >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    echo 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    ac_count=`expr $ac_count + 1`
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+
+    $ac_path_EGREP_found && break 3
+  done
+done
+
+done
+IFS=$as_save_IFS
+
+
+fi
+
+EGREP="$ac_cv_path_EGREP"
+if test -z "$EGREP"; then
+  { { echo "$as_me:$LINENO: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5
+echo "$as_me: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+else
+  ac_cv_path_EGREP=$EGREP
+fi
+
+
+   fi
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_path_EGREP" >&5
+echo "${ECHO_T}$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ echo "$as_me:$LINENO: checking for ANSI C header files" >&5
+echo $ECHO_N "checking for ANSI C header files... $ECHO_C" >&6; }
+if test "${ac_cv_header_stdc+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_header_stdc=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cv_header_stdc=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then
+  :
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then
+  :
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then
+  :
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+                  (('a' <= (c) && (c) <= 'i') \
+                    || ('j' <= (c) && (c) <= 'r') \
+                    || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+       || toupper (i) != TOUPPER (i))
+      return 2;
+  return 0;
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  :
+else
+  echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+
+
+fi
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5
+echo "${ECHO_T}$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+cat >>confdefs.h <<\_ACEOF
+#define STDC_HEADERS 1
+_ACEOF
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+
+
+
+
+
+
+
+
+
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+                 inttypes.h stdint.h unistd.h
+do
+as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh`
+{ echo "$as_me:$LINENO: checking for $ac_header" >&5
+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; }
+if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+
+#include <$ac_header>
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  eval "$as_ac_Header=yes"
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       eval "$as_ac_Header=no"
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+ac_res=`eval echo '${'$as_ac_Header'}'`
+              { echo "$as_me:$LINENO: result: $ac_res" >&5
+echo "${ECHO_T}$ac_res" >&6; }
+if test `eval echo '${'$as_ac_Header'}'` = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+{ echo "$as_me:$LINENO: checking whether byte ordering is bigendian" >&5
+echo $ECHO_N "checking whether byte ordering is bigendian... $ECHO_C" >&6; }
+if test "${ac_cv_c_bigendian+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  # See if sys/param.h defines the BYTE_ORDER macro.
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <sys/types.h>
+#include <sys/param.h>
+
+int
+main ()
+{
+#if  ! (defined BYTE_ORDER && defined BIG_ENDIAN && defined LITTLE_ENDIAN \
+       && BYTE_ORDER && BIG_ENDIAN && LITTLE_ENDIAN)
+ bogus endian macros
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  # It does; now see whether it defined to BIG_ENDIAN or not.
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <sys/types.h>
+#include <sys/param.h>
+
+int
+main ()
+{
+#if BYTE_ORDER != BIG_ENDIAN
+ not big endian
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_c_bigendian=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cv_c_bigendian=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       # It does not; compile a test program.
+if test "$cross_compiling" = yes; then
+  # try to guess the endianness by grepping values into an object file
+  ac_cv_c_bigendian=unknown
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+short int ascii_mm[] = { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
+short int ascii_ii[] = { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
+void _ascii () { char *s = (char *) ascii_mm; s = (char *) ascii_ii; }
+short int ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
+short int ebcdic_mm[] = { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
+void _ebcdic () { char *s = (char *) ebcdic_mm; s = (char *) ebcdic_ii; }
+int
+main ()
+{
+ _ascii (); _ebcdic ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  if grep BIGenDianSyS conftest.$ac_objext >/dev/null ; then
+  ac_cv_c_bigendian=yes
+fi
+if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then
+  if test "$ac_cv_c_bigendian" = unknown; then
+    ac_cv_c_bigendian=no
+  else
+    # finding both strings is unlikely to happen, but who knows?
+    ac_cv_c_bigendian=unknown
+  fi
+fi
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+
+  /* Are we little or big endian?  From Harbison&Steele.  */
+  union
+  {
+    long int l;
+    char c[sizeof (long int)];
+  } u;
+  u.l = 1;
+  return u.c[sizeof (long int) - 1] == 1;
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_c_bigendian=no
+else
+  echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+ac_cv_c_bigendian=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_c_bigendian" >&5
+echo "${ECHO_T}$ac_cv_c_bigendian" >&6; }
+case $ac_cv_c_bigendian in
+  yes)
+
+cat >>confdefs.h <<\_ACEOF
+#define WORDS_BIGENDIAN 1
+_ACEOF
+ ;;
+  no)
+     ;;
+  *)
+    { { echo "$as_me:$LINENO: error: unknown endianness
+presetting ac_cv_c_bigendian=no (or yes) will help" >&5
+echo "$as_me: error: unknown endianness
+presetting ac_cv_c_bigendian=no (or yes) will help" >&2;}
+   { (exit 1); exit 1; }; } ;;
+esac
+
+
+HAVE_C9X=yes
+if test x"$c9x_ints" = x"no"; then
+   { echo "$as_me:$LINENO: checking for char" >&5
+echo $ECHO_N "checking for char... $ECHO_C" >&6; }
+if test "${ac_cv_type_char+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+typedef char ac__type_new_;
+int
+main ()
+{
+if ((ac__type_new_ *) 0)
+  return 0;
+if (sizeof (ac__type_new_))
+  return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_type_char=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cv_type_char=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_type_char" >&5
+echo "${ECHO_T}$ac_cv_type_char" >&6; }
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ echo "$as_me:$LINENO: checking size of char" >&5
+echo $ECHO_N "checking size of char... $ECHO_C" >&6; }
+if test "${ac_cv_sizeof_char+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test "$cross_compiling" = yes; then
+  # Depending upon the size, compute the lo and hi bounds.
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef char ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=0 ac_mid=0
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef char ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr $ac_mid + 1`
+                       if test $ac_lo -le $ac_mid; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef char ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) < 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=-1 ac_mid=-1
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef char ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_hi=`expr '(' $ac_mid ')' - 1`
+                       if test $ac_mid -le $ac_hi; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo= ac_hi=
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+# Binary search between lo and hi bounds.
+while test "x$ac_lo" != "x$ac_hi"; do
+  ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo`
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef char ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr '(' $ac_mid ')' + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+case $ac_lo in
+?*) ac_cv_sizeof_char=$ac_lo;;
+'') if test "$ac_cv_type_char" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (char)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (char)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_char=0
+   fi ;;
+esac
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef char ac__type_sizeof_;
+static long int longval () { return (long int) (sizeof (ac__type_sizeof_)); }
+static unsigned long int ulongval () { return (long int) (sizeof (ac__type_sizeof_)); }
+#include <stdio.h>
+#include <stdlib.h>
+int
+main ()
+{
+
+  FILE *f = fopen ("conftest.val", "w");
+  if (! f)
+    return 1;
+  if (((long int) (sizeof (ac__type_sizeof_))) < 0)
+    {
+      long int i = longval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%ld\n", i);
+    }
+  else
+    {
+      unsigned long int i = ulongval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%lu\n", i);
+    }
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_sizeof_char=`cat conftest.val`
+else
+  echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+if test "$ac_cv_type_char" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (char)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (char)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_char=0
+   fi
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f conftest.val
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_sizeof_char" >&5
+echo "${ECHO_T}$ac_cv_sizeof_char" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_CHAR $ac_cv_sizeof_char
+_ACEOF
+
+
+   { echo "$as_me:$LINENO: checking for int" >&5
+echo $ECHO_N "checking for int... $ECHO_C" >&6; }
+if test "${ac_cv_type_int+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+typedef int ac__type_new_;
+int
+main ()
+{
+if ((ac__type_new_ *) 0)
+  return 0;
+if (sizeof (ac__type_new_))
+  return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_type_int=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cv_type_int=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_type_int" >&5
+echo "${ECHO_T}$ac_cv_type_int" >&6; }
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ echo "$as_me:$LINENO: checking size of int" >&5
+echo $ECHO_N "checking size of int... $ECHO_C" >&6; }
+if test "${ac_cv_sizeof_int+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test "$cross_compiling" = yes; then
+  # Depending upon the size, compute the lo and hi bounds.
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef int ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=0 ac_mid=0
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef int ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr $ac_mid + 1`
+                       if test $ac_lo -le $ac_mid; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef int ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) < 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=-1 ac_mid=-1
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef int ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_hi=`expr '(' $ac_mid ')' - 1`
+                       if test $ac_mid -le $ac_hi; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo= ac_hi=
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+# Binary search between lo and hi bounds.
+while test "x$ac_lo" != "x$ac_hi"; do
+  ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo`
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef int ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr '(' $ac_mid ')' + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+case $ac_lo in
+?*) ac_cv_sizeof_int=$ac_lo;;
+'') if test "$ac_cv_type_int" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (int)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (int)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_int=0
+   fi ;;
+esac
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef int ac__type_sizeof_;
+static long int longval () { return (long int) (sizeof (ac__type_sizeof_)); }
+static unsigned long int ulongval () { return (long int) (sizeof (ac__type_sizeof_)); }
+#include <stdio.h>
+#include <stdlib.h>
+int
+main ()
+{
+
+  FILE *f = fopen ("conftest.val", "w");
+  if (! f)
+    return 1;
+  if (((long int) (sizeof (ac__type_sizeof_))) < 0)
+    {
+      long int i = longval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%ld\n", i);
+    }
+  else
+    {
+      unsigned long int i = ulongval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%lu\n", i);
+    }
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_sizeof_int=`cat conftest.val`
+else
+  echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+if test "$ac_cv_type_int" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (int)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (int)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_int=0
+   fi
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f conftest.val
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_sizeof_int" >&5
+echo "${ECHO_T}$ac_cv_sizeof_int" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_INT $ac_cv_sizeof_int
+_ACEOF
+
+
+   { echo "$as_me:$LINENO: checking for short" >&5
+echo $ECHO_N "checking for short... $ECHO_C" >&6; }
+if test "${ac_cv_type_short+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+typedef short ac__type_new_;
+int
+main ()
+{
+if ((ac__type_new_ *) 0)
+  return 0;
+if (sizeof (ac__type_new_))
+  return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_type_short=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cv_type_short=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_type_short" >&5
+echo "${ECHO_T}$ac_cv_type_short" >&6; }
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ echo "$as_me:$LINENO: checking size of short" >&5
+echo $ECHO_N "checking size of short... $ECHO_C" >&6; }
+if test "${ac_cv_sizeof_short+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test "$cross_compiling" = yes; then
+  # Depending upon the size, compute the lo and hi bounds.
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef short ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=0 ac_mid=0
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef short ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr $ac_mid + 1`
+                       if test $ac_lo -le $ac_mid; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef short ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) < 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=-1 ac_mid=-1
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef short ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_hi=`expr '(' $ac_mid ')' - 1`
+                       if test $ac_mid -le $ac_hi; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo= ac_hi=
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+# Binary search between lo and hi bounds.
+while test "x$ac_lo" != "x$ac_hi"; do
+  ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo`
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef short ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr '(' $ac_mid ')' + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+case $ac_lo in
+?*) ac_cv_sizeof_short=$ac_lo;;
+'') if test "$ac_cv_type_short" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (short)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (short)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_short=0
+   fi ;;
+esac
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef short ac__type_sizeof_;
+static long int longval () { return (long int) (sizeof (ac__type_sizeof_)); }
+static unsigned long int ulongval () { return (long int) (sizeof (ac__type_sizeof_)); }
+#include <stdio.h>
+#include <stdlib.h>
+int
+main ()
+{
+
+  FILE *f = fopen ("conftest.val", "w");
+  if (! f)
+    return 1;
+  if (((long int) (sizeof (ac__type_sizeof_))) < 0)
+    {
+      long int i = longval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%ld\n", i);
+    }
+  else
+    {
+      unsigned long int i = ulongval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%lu\n", i);
+    }
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_sizeof_short=`cat conftest.val`
+else
+  echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+if test "$ac_cv_type_short" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (short)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (short)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_short=0
+   fi
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f conftest.val
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_sizeof_short" >&5
+echo "${ECHO_T}$ac_cv_sizeof_short" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_SHORT $ac_cv_sizeof_short
+_ACEOF
+
+
+   { echo "$as_me:$LINENO: checking for long" >&5
+echo $ECHO_N "checking for long... $ECHO_C" >&6; }
+if test "${ac_cv_type_long+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+typedef long ac__type_new_;
+int
+main ()
+{
+if ((ac__type_new_ *) 0)
+  return 0;
+if (sizeof (ac__type_new_))
+  return 0;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_cv_type_long=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_cv_type_long=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_type_long" >&5
+echo "${ECHO_T}$ac_cv_type_long" >&6; }
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ echo "$as_me:$LINENO: checking size of long" >&5
+echo $ECHO_N "checking size of long... $ECHO_C" >&6; }
+if test "${ac_cv_sizeof_long+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test "$cross_compiling" = yes; then
+  # Depending upon the size, compute the lo and hi bounds.
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef long ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=0 ac_mid=0
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef long ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr $ac_mid + 1`
+                       if test $ac_lo -le $ac_mid; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef long ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) < 0)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=-1 ac_mid=-1
+  while :; do
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef long ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) >= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_lo=$ac_mid; break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_hi=`expr '(' $ac_mid ')' - 1`
+                       if test $ac_mid -le $ac_hi; then
+                         ac_lo= ac_hi=
+                         break
+                       fi
+                       ac_mid=`expr 2 '*' $ac_mid`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo= ac_hi=
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+# Binary search between lo and hi bounds.
+while test "x$ac_lo" != "x$ac_hi"; do
+  ac_mid=`expr '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo`
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef long ac__type_sizeof_;
+int
+main ()
+{
+static int test_array [1 - 2 * !(((long int) (sizeof (ac__type_sizeof_))) <= $ac_mid)];
+test_array [0] = 0
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_compile") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+        test -z "$ac_cxx_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then
+  ac_hi=$ac_mid
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_lo=`expr '(' $ac_mid ')' + 1`
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+case $ac_lo in
+?*) ac_cv_sizeof_long=$ac_lo;;
+'') if test "$ac_cv_type_long" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (long)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (long)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_long=0
+   fi ;;
+esac
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+   typedef long ac__type_sizeof_;
+static long int longval () { return (long int) (sizeof (ac__type_sizeof_)); }
+static unsigned long int ulongval () { return (long int) (sizeof (ac__type_sizeof_)); }
+#include <stdio.h>
+#include <stdlib.h>
+int
+main ()
+{
+
+  FILE *f = fopen ("conftest.val", "w");
+  if (! f)
+    return 1;
+  if (((long int) (sizeof (ac__type_sizeof_))) < 0)
+    {
+      long int i = longval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%ld\n", i);
+    }
+  else
+    {
+      unsigned long int i = ulongval ();
+      if (i != ((long int) (sizeof (ac__type_sizeof_))))
+       return 1;
+      fprintf (f, "%lu\n", i);
+    }
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_sizeof_long=`cat conftest.val`
+else
+  echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+if test "$ac_cv_type_long" = yes; then
+     { { echo "$as_me:$LINENO: error: cannot compute sizeof (long)
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute sizeof (long)
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+   else
+     ac_cv_sizeof_long=0
+   fi
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f conftest.val
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_sizeof_long" >&5
+echo "${ECHO_T}$ac_cv_sizeof_long" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_LONG $ac_cv_sizeof_long
+_ACEOF
+
+
+
+   HAVE_C9X=
+
+fi
+
+# Extract the first word of "debiandoc2html", so it can be a program name with args.
+set dummy debiandoc2html; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_DEBIANDOC_HTML+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$DEBIANDOC_HTML"; then
+  ac_cv_prog_DEBIANDOC_HTML="$DEBIANDOC_HTML" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_DEBIANDOC_HTML=""yes""
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_prog_DEBIANDOC_HTML" && ac_cv_prog_DEBIANDOC_HTML=""""
+fi
+fi
+DEBIANDOC_HTML=$ac_cv_prog_DEBIANDOC_HTML
+if test -n "$DEBIANDOC_HTML"; then
+  { echo "$as_me:$LINENO: result: $DEBIANDOC_HTML" >&5
+echo "${ECHO_T}$DEBIANDOC_HTML" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+# Extract the first word of "debiandoc2text", so it can be a program name with args.
+set dummy debiandoc2text; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_DEBIANDOC_TEXT+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$DEBIANDOC_TEXT"; then
+  ac_cv_prog_DEBIANDOC_TEXT="$DEBIANDOC_TEXT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_DEBIANDOC_TEXT=""yes""
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_prog_DEBIANDOC_TEXT" && ac_cv_prog_DEBIANDOC_TEXT=""""
+fi
+fi
+DEBIANDOC_TEXT=$ac_cv_prog_DEBIANDOC_TEXT
+if test -n "$DEBIANDOC_TEXT"; then
+  { echo "$as_me:$LINENO: result: $DEBIANDOC_TEXT" >&5
+echo "${ECHO_T}$DEBIANDOC_TEXT" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+
+# Extract the first word of "yodl2man", so it can be a program name with args.
+set dummy yodl2man; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_prog_YODL_MAN+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$YODL_MAN"; then
+  ac_cv_prog_YODL_MAN="$YODL_MAN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_YODL_MAN=""yes""
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_prog_YODL_MAN" && ac_cv_prog_YODL_MAN=""""
+fi
+fi
+YODL_MAN=$ac_cv_prog_YODL_MAN
+if test -n "$YODL_MAN"; then
+  { echo "$as_me:$LINENO: result: $YODL_MAN" >&5
+echo "${ECHO_T}$YODL_MAN" >&6; }
+else
+  { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+
+ac_config_files="$ac_config_files environment.mak:buildlib/environment.mak.in makefile:buildlib/makefile.in"
+
+ac_config_commands="$ac_config_commands default"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5
+echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      *) $as_unset $ac_var ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes (double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \).
+      sed -n \
+       "s/'/'\\\\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    test "x$cache_file" != "x/dev/null" &&
+      { echo "$as_me:$LINENO: updating cache $cache_file" >&5
+echo "$as_me: updating cache $cache_file" >&6;}
+    cat confcache >$cache_file
+  else
+    { echo "$as_me:$LINENO: not updating unwritable cache $cache_file" >&5
+echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  ac_libobjs="$ac_libobjs \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  ac_ltlibobjs="$ac_ltlibobjs \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+: ${CONFIG_STATUS=./config.status}
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5
+echo "$as_me: creating $CONFIG_STATUS" >&6;}
+cat >$CONFIG_STATUS <<_ACEOF
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+SHELL=\${CONFIG_SHELL-$SHELL}
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+## --------------------- ##
+## M4sh Initialization.  ##
+## --------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in
+  *posix*) set -o posix ;;
+esac
+
+fi
+
+
+
+
+# PATH needs CR
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+    PATH_SEPARATOR=';'
+  else
+    PATH_SEPARATOR=:
+  fi
+  rm -f conf$$.sh
+fi
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  as_unset=unset
+else
+  as_unset=false
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+as_nl='
+'
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+case $0 in
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  { (exit 1); exit 1; }
+fi
+
+# Work around bugs in pre-3.0 UWIN ksh.
+for as_var in ENV MAIL MAILPATH
+do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+  LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+  LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+  LC_TELEPHONE LC_TIME
+do
+  if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+    eval $as_var=C; export $as_var
+  else
+    ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var
+  fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# CDPATH.
+$as_unset CDPATH
+
+
+
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || {
+
+  # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+  # uniformly replaced by the line number.  The first 'sed' inserts a
+  # line-number line after each line using $LINENO; the second 'sed'
+  # does the real work.  The second script uses 'N' to pair each
+  # line-number line with the line containing $LINENO, and appends
+  # trailing '-' during substitution so that $LINENO is not a special
+  # case at line end.
+  # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+  # scripts with optimization help from Paolo Bonzini.  Blame Lee
+  # E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2
+   { (exit 1); exit 1; }; }
+
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in
+-n*)
+  case `echo 'x\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  *)   ECHO_C='\c';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir
+fi
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+  as_ln_s='ln -s'
+  # ... but there are two gotchas:
+  # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+  # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+  # In both cases, we have to default to `cp -p'.
+  ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+    as_ln_s='cp -p'
+elif ln conf$$.file conf$$ 2>/dev/null; then
+  as_ln_s=ln
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p=:
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+        test -d "$1/.";
+      else
+       case $1 in
+        -*)set "./$1";;
+       esac;
+       case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in
+       ???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+
+# Save the log message, to keep $[0] and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by $as_me, which was
+generated by GNU Autoconf 2.61.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+ac_cs_usage="\
+\`$as_me' instantiates files from templates according to the
+current configuration.
+
+Usage: $0 [OPTIONS] [FILE]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+  -q, --quiet      do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+  --file=FILE[:TEMPLATE]
+                  instantiate the configuration file FILE
+  --header=FILE[:TEMPLATE]
+                  instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Configuration commands:
+$config_commands
+
+Report bugs to <bug-autoconf@gnu.org>."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+ac_cs_version="\\
+config.status
+configured by $0, generated by GNU Autoconf 2.61,
+  with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
+
+Copyright (C) 2006 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+# If no file are specified by the user, then we need to provide default
+# value.  By we need to know if files were specified by the user.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    echo "$ac_cs_version"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    CONFIG_FILES="$CONFIG_FILES $ac_optarg"
+    ac_need_defaults=false;;
+  --header | --heade | --head | --hea )
+    $ac_shift
+    CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg"
+    ac_need_defaults=false;;
+  --he | --h)
+    # Conflict between --help and --header
+    { echo "$as_me: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&2
+   { (exit 1); exit 1; }; };;
+  --help | --hel | -h )
+    echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) { echo "$as_me: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&2
+   { (exit 1); exit 1; }; } ;;
+
+  *) ac_config_targets="$ac_config_targets $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+if \$ac_cs_recheck; then
+  echo "running CONFIG_SHELL=$SHELL $SHELL $0 "$ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6
+  CONFIG_SHELL=$SHELL
+  export CONFIG_SHELL
+  exec $SHELL "$0"$ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "include/config.h") CONFIG_HEADERS="$CONFIG_HEADERS include/config.h:buildlib/config.h.in" ;;
+    "environment.mak") CONFIG_FILES="$CONFIG_FILES environment.mak:buildlib/environment.mak.in" ;;
+    "makefile") CONFIG_FILES="$CONFIG_FILES makefile:buildlib/makefile.in" ;;
+    "default") CONFIG_COMMANDS="$CONFIG_COMMANDS default" ;;
+
+  *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
+echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
+   { (exit 1); exit 1; }; };;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+  test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+  test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp=
+  trap 'exit_status=$?
+  { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status
+' 0
+  trap '{ (exit 1); exit 1; }' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -n "$tmp" && test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} ||
+{
+   echo "$me: cannot create a temporary directory in ." >&2
+   { (exit 1); exit 1; }
+}
+
+#
+# Set up the sed scripts for CONFIG_FILES section.
+#
+
+# No need to generate the scripts if there are no CONFIG_FILES.
+# This happens for instance when ./config.status config.h
+if test -n "$CONFIG_FILES"; then
+
+_ACEOF
+
+
+
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  cat >conf$$subs.sed <<_ACEOF
+SHELL!$SHELL$ac_delim
+PATH_SEPARATOR!$PATH_SEPARATOR$ac_delim
+PACKAGE_NAME!$PACKAGE_NAME$ac_delim
+PACKAGE_TARNAME!$PACKAGE_TARNAME$ac_delim
+PACKAGE_VERSION!$PACKAGE_VERSION$ac_delim
+PACKAGE_STRING!$PACKAGE_STRING$ac_delim
+PACKAGE_BUGREPORT!$PACKAGE_BUGREPORT$ac_delim
+exec_prefix!$exec_prefix$ac_delim
+prefix!$prefix$ac_delim
+program_transform_name!$program_transform_name$ac_delim
+bindir!$bindir$ac_delim
+sbindir!$sbindir$ac_delim
+libexecdir!$libexecdir$ac_delim
+datarootdir!$datarootdir$ac_delim
+datadir!$datadir$ac_delim
+sysconfdir!$sysconfdir$ac_delim
+sharedstatedir!$sharedstatedir$ac_delim
+localstatedir!$localstatedir$ac_delim
+includedir!$includedir$ac_delim
+oldincludedir!$oldincludedir$ac_delim
+docdir!$docdir$ac_delim
+infodir!$infodir$ac_delim
+htmldir!$htmldir$ac_delim
+dvidir!$dvidir$ac_delim
+pdfdir!$pdfdir$ac_delim
+psdir!$psdir$ac_delim
+libdir!$libdir$ac_delim
+localedir!$localedir$ac_delim
+mandir!$mandir$ac_delim
+DEFS!$DEFS$ac_delim
+ECHO_C!$ECHO_C$ac_delim
+ECHO_N!$ECHO_N$ac_delim
+ECHO_T!$ECHO_T$ac_delim
+LIBS!$LIBS$ac_delim
+build_alias!$build_alias$ac_delim
+host_alias!$host_alias$ac_delim
+target_alias!$target_alias$ac_delim
+CC!$CC$ac_delim
+CFLAGS!$CFLAGS$ac_delim
+LDFLAGS!$LDFLAGS$ac_delim
+CPPFLAGS!$CPPFLAGS$ac_delim
+ac_ct_CC!$ac_ct_CC$ac_delim
+EXEEXT!$EXEEXT$ac_delim
+OBJEXT!$OBJEXT$ac_delim
+build!$build$ac_delim
+build_cpu!$build_cpu$ac_delim
+build_vendor!$build_vendor$ac_delim
+build_os!$build_os$ac_delim
+host!$host$ac_delim
+host_cpu!$host_cpu$ac_delim
+host_vendor!$host_vendor$ac_delim
+host_os!$host_os$ac_delim
+CXX!$CXX$ac_delim
+CXXFLAGS!$CXXFLAGS$ac_delim
+ac_ct_CXX!$ac_ct_CXX$ac_delim
+AR!$AR$ac_delim
+PTHREADLIB!$PTHREADLIB$ac_delim
+CXXCPP!$CXXCPP$ac_delim
+GREP!$GREP$ac_delim
+EGREP!$EGREP$ac_delim
+HAVE_C9X!$HAVE_C9X$ac_delim
+DEBIANDOC_HTML!$DEBIANDOC_HTML$ac_delim
+DEBIANDOC_TEXT!$DEBIANDOC_TEXT$ac_delim
+YODL_MAN!$YODL_MAN$ac_delim
+LIBOBJS!$LIBOBJS$ac_delim
+LTLIBOBJS!$LTLIBOBJS$ac_delim
+_ACEOF
+
+  if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 66; then
+    break
+  elif $ac_last_try; then
+    { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5
+echo "$as_me: error: could not make $CONFIG_STATUS" >&2;}
+   { (exit 1); exit 1; }; }
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+
+ac_eof=`sed -n '/^CEOF[0-9]*$/s/CEOF/0/p' conf$$subs.sed`
+if test -n "$ac_eof"; then
+  ac_eof=`echo "$ac_eof" | sort -nru | sed 1q`
+  ac_eof=`expr $ac_eof + 1`
+fi
+
+cat >>$CONFIG_STATUS <<_ACEOF
+cat >"\$tmp/subs-1.sed" <<\CEOF$ac_eof
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b end
+_ACEOF
+sed '
+s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g
+s/^/s,@/; s/!/@,|#_!!_#|/
+:n
+t n
+s/'"$ac_delim"'$/,g/; t
+s/$/\\/; p
+N; s/^.*\n//; s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g; b n
+' >>$CONFIG_STATUS <conf$$subs.sed
+rm -f conf$$subs.sed
+cat >>$CONFIG_STATUS <<_ACEOF
+:end
+s/|#_!!_#|//g
+CEOF$ac_eof
+_ACEOF
+
+
+# VPATH may cause trouble with some makes, so we remove $(srcdir),
+# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[         ]*VPATH[        ]*=/{
+s/:*\$(srcdir):*/:/
+s/:*\${srcdir}:*/:/
+s/:*@srcdir@:*/:/
+s/^\([^=]*=[    ]*\):*/\1/
+s/:*$//
+s/^[^=]*=[      ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+fi # test -n "$CONFIG_FILES"
+
+
+for ac_tag in  :F $CONFIG_FILES  :H $CONFIG_HEADERS    :C $CONFIG_COMMANDS
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) { { echo "$as_me:$LINENO: error: Invalid tag $ac_tag." >&5
+echo "$as_me: error: Invalid tag $ac_tag." >&2;}
+   { (exit 1); exit 1; }; };;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+        # (if the path is not absolute).  The absolute path cannot be DOS-style,
+        # because $ac_f cannot contain `:'.
+        test -f "$ac_f" ||
+          case $ac_f in
+          [\\/$]*) false;;
+          *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+          esac ||
+          { { echo "$as_me:$LINENO: error: cannot find input file: $ac_f" >&5
+echo "$as_me: error: cannot find input file: $ac_f" >&2;}
+   { (exit 1); exit 1; }; };;
+      esac
+      ac_file_inputs="$ac_file_inputs $ac_f"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input="Generated from "`IFS=:
+         echo $* | sed 's|^[^:]*/||;s|:[^:]*/|, |g'`" by configure."
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+    fi
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$tmp/stdin";;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$ac_file" : 'X\(//\)[^/]' \| \
+        X"$ac_file" : 'X\(//\)$' \| \
+        X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  { as_dir="$ac_dir"
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || { { echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5
+echo "$as_me: error: cannot create directory $as_dir" >&2;}
+   { (exit 1); exit 1; }; }; }
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+
+case `sed -n '/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p
+' $ac_file_inputs` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { echo "$as_me:$LINENO: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+    s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF
+  sed "$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s&@configure_input@&$configure_input&;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+$ac_datarootdir_hack
+" $ac_file_inputs | sed -f "$tmp/subs-1.sed" >$tmp/out
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } &&
+  { echo "$as_me:$LINENO: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined." >&5
+echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined." >&2;}
+
+  rm -f "$tmp/stdin"
+  case $ac_file in
+  -) cat "$tmp/out"; rm -f "$tmp/out";;
+  *) rm -f "$ac_file"; mv "$tmp/out" $ac_file;;
+  esac
+ ;;
+  :H)
+  #
+  # CONFIG_HEADER
+  #
+_ACEOF
+
+# Transform confdefs.h into a sed script `conftest.defines', that
+# substitutes the proper values into config.h.in to produce config.h.
+rm -f conftest.defines conftest.tail
+# First, append a space to every undef/define line, to ease matching.
+echo 's/$/ /' >conftest.defines
+# Then, protect against being on the right side of a sed subst, or in
+# an unquoted here document, in config.status.  If some macros were
+# called several times there might be several #defines for the same
+# symbol, which is useless.  But do not sort them, since the last
+# AC_DEFINE must be honored.
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+# These sed commands are passed to sed as "A NAME B PARAMS C VALUE D", where
+# NAME is the cpp macro being defined, VALUE is the value it is being given.
+# PARAMS is the parameter list in the macro definition--in most cases, it's
+# just an empty string.
+ac_dA='s,^\\([  #]*\\)[^        ]*\\([  ]*'
+ac_dB='\\)[     (].*,\\1define\\2'
+ac_dC=' '
+ac_dD=' ,'
+
+uniq confdefs.h |
+  sed -n '
+       t rset
+       :rset
+       s/^[     ]*#[    ]*define[       ][      ]*//
+       t ok
+       d
+       :ok
+       s/[\\&,]/\\&/g
+       s/^\('"$ac_word_re"'\)\(([^()]*)\)[      ]*\(.*\)/ '"$ac_dA"'\1'"$ac_dB"'\2'"${ac_dC}"'\3'"$ac_dD"'/p
+       s/^\('"$ac_word_re"'\)[  ]*\(.*\)/'"$ac_dA"'\1'"$ac_dB$ac_dC"'\2'"$ac_dD"'/p
+  ' >>conftest.defines
+
+# Remove the space that was appended to ease matching.
+# Then replace #undef with comments.  This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+# (The regexp can be short, since the line contains either #define or #undef.)
+echo 's/ $//
+s,^[    #]*u.*,/* & */,' >>conftest.defines
+
+# Break up conftest.defines:
+ac_max_sed_lines=50
+
+# First sed command is:         sed -f defines.sed $ac_file_inputs >"$tmp/out1"
+# Second one is:        sed -f defines.sed "$tmp/out1" >"$tmp/out2"
+# Third one will be:    sed -f defines.sed "$tmp/out2" >"$tmp/out1"
+# et cetera.
+ac_in='$ac_file_inputs'
+ac_out='"$tmp/out1"'
+ac_nxt='"$tmp/out2"'
+
+while :
+do
+  # Write a here document:
+    cat >>$CONFIG_STATUS <<_ACEOF
+    # First, check the format of the line:
+    cat >"\$tmp/defines.sed" <<\\CEOF
+/^[     ]*#[    ]*undef[        ][      ]*$ac_word_re[  ]*\$/b def
+/^[     ]*#[    ]*define[       ][      ]*$ac_word_re[(         ]/b def
+b
+:def
+_ACEOF
+  sed ${ac_max_sed_lines}q conftest.defines >>$CONFIG_STATUS
+  echo 'CEOF
+    sed -f "$tmp/defines.sed"' "$ac_in >$ac_out" >>$CONFIG_STATUS
+  ac_in=$ac_out; ac_out=$ac_nxt; ac_nxt=$ac_in
+  sed 1,${ac_max_sed_lines}d conftest.defines >conftest.tail
+  grep . conftest.tail >/dev/null || break
+  rm -f conftest.defines
+  mv conftest.tail conftest.defines
+done
+rm -f conftest.defines conftest.tail
+
+echo "ac_result=$ac_in" >>$CONFIG_STATUS
+cat >>$CONFIG_STATUS <<\_ACEOF
+  if test x"$ac_file" != x-; then
+    echo "/* $configure_input  */" >"$tmp/config.h"
+    cat "$ac_result" >>"$tmp/config.h"
+    if diff $ac_file "$tmp/config.h" >/dev/null 2>&1; then
+      { echo "$as_me:$LINENO: $ac_file is unchanged" >&5
+echo "$as_me: $ac_file is unchanged" >&6;}
+    else
+      rm -f $ac_file
+      mv "$tmp/config.h" $ac_file
+    fi
+  else
+    echo "/* $configure_input  */"
+    cat "$ac_result"
+  fi
+  rm -f "$tmp/out12"
+ ;;
+
+  :C)  { echo "$as_me:$LINENO: executing $ac_file commands" >&5
+echo "$as_me: executing $ac_file commands" >&6;}
+ ;;
+  esac
+
+
+  case $ac_file$ac_mode in
+    "default":C) make dirs ;;
+
+  esac
+done # for ac_tag
+
+
+{ (exit 0); exit 0; }
+_ACEOF
+chmod +x $CONFIG_STATUS
+ac_clean_files=$ac_clean_files_save
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || { (exit 1); exit 1; }
+fi
+
diff --git a/tools/dsync-0.0/configure.in b/tools/dsync-0.0/configure.in
new file mode 100644 (file)
index 0000000..864aec0
--- /dev/null
@@ -0,0 +1,101 @@
+ad
+dnl Process this file with autoconf to produce a configure script.
+dnl The ONLY thing this is used for is to configure for different
+dnl linux architectures and configurations, it is not used to make the
+dnl code more portable
+
+dnl You MUST have an environment that has all the POSIX functions and
+dnl some of the more popular bsd/sysv ones (like select). You'll also
+dnl need a C++ compiler that is semi-standard conformant, exceptions are 
+dnl not used but STL is.
+
+dnl 'make -f Makefile startup' will generate the configure file from 
+dnl configure.in correctly and can be run at any time
+
+AC_INIT(configure.in)
+AC_CONFIG_AUX_DIR(buildlib)
+AC_CONFIG_HEADER(include/config.h:buildlib/config.h.in)
+
+dnl -- SET THIS TO THE RELEASE VERSION --
+AC_DEFINE_UNQUOTED(VERSION,"0.0")
+AC_DEFINE_UNQUOTED(PACKAGE,"dsync")
+
+AC_CHECK_TOOL_PREFIX   dnl Initial guess
+
+dnl Check our C compiler
+AC_PROG_CC
+AC_ISC_POSIX
+
+dnl Check the host arch (build+target not needed... yet)
+AC_CANONICAL_HOST
+AC_CHECK_TOOL_PREFIX   dnl recheck, in case the initial guess was wrong
+
+dnl Check for other programs
+AC_PROG_CXX
+AC_LANG_CPLUSPLUS
+AC_CHECK_TOOL(AR,ar, :)
+
+if test "$AR" = ":"; then
+     AC_MSG_ERROR(failed: Sorry I could not find ar in the path)
+fi
+
+dnl Checks for pthread
+AC_CHECK_LIB(pthread, pthread_create,[AC_DEFINE(HAVE_PTHREAD) PTHREADLIB="-lpthread"])
+AC_SUBST(PTHREADLIB)
+
+dnl Converts the ARCH to be the same as dpkg
+AC_MSG_CHECKING(system architecture)
+archset="`awk '$1 == "'$host_cpu'" { print $2 }' $srcdir/buildlib/archtable`"
+if test "x$archset" = "x"; then
+  AC_MSG_ERROR(failed: use --host=)
+fi
+AC_MSG_RESULT($archset)
+AC_DEFINE_UNQUOTED(ARCHITECTURE,"$archset")
+
+dnl We use C9x types if at all possible
+AC_CACHE_CHECK([for C9x integer types],c9x_ints,[
+    AC_TRY_COMPILE([#include <inttypes.h>],
+                   [uint8_t Foo1;uint16_t Foo2;uint32_t Foo3;uint64_t Foo],
+                  c9x_ints=yes,c9x_ints=no)])
+
+dnl Check the sizes etc. of the architecture
+changequote(,)
+if archline="`sed -ne 's/^'$archset':[         ]\+\(.*\)/\1/gp' $srcdir/buildlib/sizetable`"; then
+   changequote([,])
+   set $archline
+   if test "$1" = "little"; then
+      ac_cv_c_bigendian=no
+   else
+      ac_cv_c_bigendian=yes
+   fi
+   size_char=$2
+   size_int=$3
+   size_short=$4
+   size_long=$5
+fi
+
+if test "$cross_compiling" = "yes" -a "$archline" = ""; then
+  AC_MSG_ERROR(When cross compiling, architecture must be present in sizetable)
+fi
+AC_C_BIGENDIAN
+   
+dnl We do not need this if we have inttypes..
+HAVE_C9X=yes
+if test x"$c9x_ints" = x"no"; then
+   AC_CHECK_SIZEOF(char,$size_char)
+   AC_CHECK_SIZEOF(int,$size_int)
+   AC_CHECK_SIZEOF(short,$size_short)
+   AC_CHECK_SIZEOF(long,$size_long)
+  
+   HAVE_C9X=
+   AC_SUBST(HAVE_C9X)
+fi
+
+dnl Check for debiandoc
+AC_CHECK_PROG(DEBIANDOC_HTML,debiandoc2html,"yes","")
+AC_CHECK_PROG(DEBIANDOC_TEXT,debiandoc2text,"yes","")
+
+dnl Check for YODL
+AC_CHECK_PROG(YODL_MAN,yodl2man,"yes","")
+
+AC_OUTPUT(environment.mak:buildlib/environment.mak.in makefile:buildlib/makefile.in,make dirs)
diff --git a/tools/dsync-0.0/debian/changelog b/tools/dsync-0.0/debian/changelog
new file mode 100644 (file)
index 0000000..247b103
--- /dev/null
@@ -0,0 +1,18 @@
+dsync (0.0-0.2) experimental; urgency=low
+
+  * Make it build with modern autoconf and upgrade to debhelper compat 4.
+
+ -- Ryan Murray <rmurray@debian.org>  Sat, 10 Nov 2007 22:07:03 +0000
+
+dsync (0.0-0.1) experimental; urgency=low
+
+  * Make it build using g++-3.3.
+
+ -- Kurt Roeckx <kurt@roeckx.be>  Mon, 16 May 2005 16:04:58 +0200
+
+dsync (0.0) experimental; urgency=low
+
+  * First experimental version.
+
+ -- Jason Gunthorpe <jgg@debian.org>  Sun, 17 Jan 1999 19:07:53 -0700 
+
diff --git a/tools/dsync-0.0/debian/compat b/tools/dsync-0.0/debian/compat
new file mode 100644 (file)
index 0000000..b8626c4
--- /dev/null
@@ -0,0 +1 @@
+4
diff --git a/tools/dsync-0.0/debian/control b/tools/dsync-0.0/debian/control
new file mode 100644 (file)
index 0000000..70e7604
--- /dev/null
@@ -0,0 +1,11 @@
+Source: dsync
+Section: net
+Priority: optional
+Maintainer: Jason Gunthorpe <jgg@debian.org>
+Standards-Version: 2.4.1
+
+Package: dsync
+Architecture: any
+Depends: ${shlibs:Depends}
+Description: Mirroring tool
+ DSync is a mirroring tool.
diff --git a/tools/dsync-0.0/debian/postinst b/tools/dsync-0.0/debian/postinst
new file mode 100755 (executable)
index 0000000..0b7518b
--- /dev/null
@@ -0,0 +1,6 @@
+#! /bin/sh
+set -e
+
+if [ "$1" = "configure" ] ; then
+  ldconfig
+fi
diff --git a/tools/dsync-0.0/debian/rules b/tools/dsync-0.0/debian/rules
new file mode 100755 (executable)
index 0000000..0c508d4
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# Made with the aid of dh_make, by Craig Small
+# Sample debian/rules that uses debhelper. GNU copyright 1997 by Joey Hess.
+# Some lines taken from debmake, by Christoph Lameter.
+# $Id: rules,v 1.2 1999/01/18 02:38:15 jgg Exp $
+
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+export DEB_HOST_GNU_TYPE  ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
+export DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
+
+# FOR AUTOCONF 2.13 ONLY
+ifeq ($(DEB_BUILD_GNU_TYPE), $(DEB_HOST_GNU_TYPE))
+#  confflags += --host=$(DEB_HOST_GNU_TYPE)
+else
+  $(error Cannot cross-compile this package out-of-the-box)
+endif
+
+build: build-stamp
+build-stamp: configure
+       dh_testdir
+       -mkdir build 
+       cd build; ../configure
+       cd ..
+       
+       # Add here commands to compile the package.
+       make
+       touch build-stamp
+
+clean:
+       dh_testdir
+#      dh_testroot
+       rm -f build-stamp
+       rm -rf build
+
+       # Add here commands to clean up after the build process.
+       -$(MAKE) clean
+       -$(MAKE) distclean
+       dh_clean
+
+# Build architecture-independent files here.
+binary-indep: build 
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build dsync
+
+dsync: build
+#      dh_testversion -pdsync
+       dh_testdir -pdsync
+       dh_testroot -pdsync
+       dh_clean -pdsync -k
+       dh_installdirs -pdsync usr/bin usr/doc/dsync usr/lib usr/doc/dsync
+
+       cp build/bin/dsync-* debian/dsync/usr/bin/
+       cp -a build/bin/libdsync.so.0.0.0 debian/dsync/usr/lib/
+       cp -a build/bin/libdsync.so.0.0 debian/dsync/usr/lib/
+       cp COPYING debian/dsync/usr/doc/dsync/copyright
+       
+       dh_installdocs -pdsync 
+       dh_installman -pdsync
+
+       dh_installchangelogs -pdsync
+       dh_strip -pdsync
+       dh_compress -pdsync
+       dh_fixperms -pdsync
+       dh_installdeb -pdsync
+       dh_makeshlibs -pdsync
+       dh_shlibdeps -pdsync
+       dh_gencontrol -pdsync
+       dh_md5sums -pdsync
+       dh_builddeb -pdsync
+
+source diff:                                                                  
+       @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+configure:
+       make startup
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary
diff --git a/tools/dsync-0.0/debian/shlibs.local b/tools/dsync-0.0/debian/shlibs.local
new file mode 100644 (file)
index 0000000..b75c86e
--- /dev/null
@@ -0,0 +1 @@
+libdsync       0
diff --git a/tools/dsync-0.0/debian/substvars b/tools/dsync-0.0/debian/substvars
new file mode 100644 (file)
index 0000000..e3698e3
--- /dev/null
@@ -0,0 +1 @@
+shlibs:Depends=libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12)
diff --git a/tools/dsync-0.0/doc/dsync-flist.1.yo b/tools/dsync-0.0/doc/dsync-flist.1.yo
new file mode 100644 (file)
index 0000000..fbb268a
--- /dev/null
@@ -0,0 +1,160 @@
+mailto(jgg@debian.org)
+manpage(dsync-flist)(1)(17 Jan 1999)(dsync)()
+manpagename(dsync)(DSync Mirroring utility -- command-line file list manipulator)
+
+manpagesynopsis()
+  dsync-flist [options] [command] [file]
+
+manpagedescription()
+
+dsync-flist is the command line tool for generating and manipulating the
+dsync file list. It can check a previosly generated list against the local
+tree and provide a report on its findings. The dsync file list is an 
+optimized binary file suitable for transmission over the internet.
+
+em(command) is one of:
+itemize(
+  it() generate em(filelist)
+  it() help
+  it() dump em(filelist)
+  it() md5sums em(filelist)
+  it() md5cache em(filelist)
+  it() lookup em(filelist dir file)
+  it() link-dups em(filelist)
+  it() verify em(filelist)
+)
+
+Unless the -h, or --help option is given one of the above commands
+must be present.
+
+startdit()
+dit(bf(generate))
+bf(generate) creates a file list. It takes as an argument the location to
+write the file list to and then procceeds to recursively scan . to produce
+the list. If md5 generation is enabled bf(generate) will use the previous
+list as a cache for md5 checksums, only building new checksums if the file
+size or timestamp has changed.
+
+dit(bf(help))
+Displays the help text
+
+dit(bf(dump))
+bf(dump) shows the contents of the given file list in a short form. The first
+word is a type field and the remaing fields represent stored information.
+The possible types are F - File, D - Directory, DM - Directory Marker, DS -
+Directory Start, H - Header, S - Device Special, L - Symlink, T - Trailer. 
+After this the actual fields are displayed. Mod - Modification time in
+seconds since the unix epoch, N - Entitiy Name, MD5 - MD5 hash, Sz - Size
+in bytes, T - Link Target, U/G - User/Group internal ID, Sig - Header
+signature, Maj - Header major number, Min - Header minor number, Epoch - 
+Internal Epoch offset, Count - Flag counter.
+
+dit(bf(md5sums))
+bf(md5sums) takes the contents of the file list and displays the stored md5 
+of every file and then the file name. This output can then be given to
+bf(md5sum -c) (GNU) to verify the checksums. Combined with the caching
+action of the file list generator this can make md5 indexes of large archives
+practical.
+
+dit(bf(md5cache))
+Like bf(md5sums), bf(md5cache) displays the md5sums of the files given
+on stdin. It will use cached MD5 information if possible otherwise it will
+compute the MD5 and return that. It is necessary to run this command from the
+same directory the file list was generated in and to give filenames relative
+to that directory. Otherwise the caching mechanism will not work.
+
+dit(bf(lookup))
+bf(lookup) searches for a single entity in the list. You must specify the
+directory, ending in / and then the entity in that directory. The output is
+the same as bf(dump)
+
+dit(bf(link-dups))
+bf(link-dups) checks the entire file list for files that have duplicate
+contents and hard links them. It does this by examining the MD5 information
+from the file list and then converting the duplicated files into a hard link.
+The file choosen to be the target of all other links is the first file
+listed in the file list. The timestamp of the new link is set to be the
+largest timestamp of all the other links and the permissions and ownership
+remain as the first link. Output is two lines per combination, the first
+indicting the source file and the second the file that will be erased and
+hardlinked, a souce file may occure multiple times if there are many
+duplicated copies.
+
+dit(bf(verify))
+bf(verify) checks the given file list against . and reports and deviations.
+
+enddit()
+
+manpageoptions()
+All command line options may be set using the configuration file, the
+descriptions indicate the configuration option to set. For boolean
+options you can override the config file by using something like bf(-f-),
+bf(--no-f), bf(-f=no) or several other variations.
+
+startdit()
+dit(bf(-h, --help))
+Show the help text
+
+dit(bf(-q, --quiet, --silent))
+Quiet; produces output suitable for logging, omitting progress indicators.
+More qs will produce more quite up to a maximum of 2. You can also use
+bf(-q=#) to set the quiet level, overriding the configuration file.
+See bf(quiet)
+
+dit(bf(-i, --include))
+dit(bf(-e, --exclude))
+Add a pattern to the ordered include list. See bf(FileList::Filter).
+
+dit(bf(-n, --no-act))
+Suppress action; No changes will be made to the local file system. This
+applies to bf(generate) and bf(verify).
+
+dit(bf(--delete))
+Allow files to be deleted; This allows files to be erased, it effects
+bf(generate) and bf(verify). See bf(delete).
+
+dit(bf(--pi, --perfer-include))
+dit(bf(--pe, --perfer-exclude))
+Add a pattern to the ordered prefer include list. See
+bf(FileList::Prefer-Filter).
+
+dit(bf(--ci, --clean-include))
+dit(bf(--ce, --clean-exclude))
+Add a pattern to the ordered clean include list. Things excluded by this
+filter will be erased. See bf(FileList::Clean-Filter).
+
+dit(bf(--md5))
+Generate md5 hashes into the list. See bf(FileList::MD5-Hashes).
+
+dit(bf(--perm))
+Generate file permissions into the list. See bf(FileList::Permissions).
+
+dit(bf(--owner))
+Generate file ownership into the list [unsupported]. See
+bf(FileList::Ownership).
+
+dit(bf(-c, --config-file))
+Configuration File; Specify a configuration file to use. bf(apt-get) will
+read the default configuration file and then this configuration file. See
+bf(apt.conf(5)) for syntax information.
+
+dit(bf(-o, --option))
+Set a Configuration Option; This will set an arbitary configuration option.
+The syntax is 
+verb(-o Foo::Bar=bar)
+
+enddit()
+
+manpageseealso()
+dsync.conf(5)
+
+manpagediagnostics()
+dsync-flist returns zero on normal operation, decimal 100 on error.
+
+manpagebugs()
+See http://bugs.debian.org/dsync.  If you wish to report a
+bug in bf(apt-get), please see bf(/usr/doc/debian/bug-reporting.txt)
+or the bf(bug(1)) command.
+
+manpageauthor()
+dsync was written by Jason Gunthorpe <jgg@debian.org>.
diff --git a/tools/dsync-0.0/doc/examples/dsync.conf b/tools/dsync-0.0/doc/examples/dsync.conf
new file mode 100644 (file)
index 0000000..76139fe
--- /dev/null
@@ -0,0 +1,55 @@
+/* This dsync configuration file is a sample that contains all options.
+   It is not ment to be used as is.
+*/
+
+/* Each module has a set of configuration parameters. The module to use
+   is specified on the command line. */
+module::Foo 
+{
+   // The base directory for the module
+   Root "/home/ftp/foo";
+   
+   // Here we specify options that control generation of the file list
+   FileList
+   {
+      // Generation options
+      MD5-Hashes "yes";
+      Hard-Links "yes";
+      Permissions "yes";
+      Ownership "yes";
+      Ordering "depth";
+      
+      /* The filter list. Items excluded by this filter are not inclued
+         in the file list */
+      Filter 
+      {
+         "+ *";
+      };
+      
+      /* The prefer filter list. Items included in the filter are prefered
+         over items exclued in this filter. This effects the order directories
+        are listed. All directories included by the filter are listed before 
+        any directories exclued  by the filter. The filter only matche
+        directories, not files. */
+      Prefer-Filter
+      {
+         "+ *";
+      };
+
+      // Use the specified pre-generated file list, relative to the root,
+      PreGenerated "dsync.list";
+   };
+   
+   // Here we specify options specific to the dsync-flist program
+   FList
+   {
+      /* This filter is used for archive maintinance, files Excluded by 
+         this filter are removed from the archive, directories are never
+        passed through */
+      Clean-Filter
+      {
+         "- core";
+         "+";
+      };
+   };
+};
diff --git a/tools/dsync-0.0/doc/filelist.sgml b/tools/dsync-0.0/doc/filelist.sgml
new file mode 100644 (file)
index 0000000..35483d1
--- /dev/null
@@ -0,0 +1,709 @@
+<!doctype debiandoc system>
+<!-- -*- mode: sgml; mode: fold -*- -->
+<book>
+<title>DSync File List Format</title>
+
+<author>Jason Gunthorpe <email>jgg@debian.org</email></author>
+<version>$Id: filelist.sgml,v 1.4 1999/11/15 07:59:49 jgg Exp $</version>
+
+<abstract>
+</abstract>
+
+<copyright>
+Copyright &copy; Jason Gunthorpe, 1998-1999.
+<p>
+DSync and this document are free software; you can redistribute them and/or
+modify them under the terms of the GNU General Public License as published
+by the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+<p>
+For more details, on Debian GNU/Linux systems, see the file
+/usr/doc/copyright/GPL for the full license.
+</copyright>
+
+<toc sect>
+
+<chapt>Introduction
+<!-- Purpose                                                          {{{ -->
+<!-- ===================================================================== -->
+<sect>Purpose
+<p>
+The DSync file list is a crucial part of the DSync system, it provides the 
+client with access to a list of files and file attributes for all the files
+in a directory tree. Much information is compacted into the per-file structure
+that may be used by the client in reconstructing the directory tree. In spirit
+it is like the common ls-lR files that mirrors have, but in practice it is 
+radically different, most striking is that it is stored in a compacted binary
+format and may optionally contain MD5 hashes.
+
+<p>
+The file list for a directory tree may be either dynamically generated by the
+server or generated only once like the ls-lR files. In fact with a static
+file list it is possible to use the <em>rsync method</> to transfer only the
+differences in the list which is a huge boon for sites with over 50000 files 
+in their directory trees
+
+<p>
+Internally the file list is stored as a series of directory blocks in no set 
+order. Each block has a relative path from the base to the directory itself
+and a list of all files in that directory. Things are not stored recursively
+so that the client can have fixed memory usage when managing the list.
+Depending on how the generator is configured the order of the directories
+may be breadth first or depth first, or perhaps completely random. The client
+should make no assumptions about the ordering of anything in the file.
+
+<p>
+Since the list may be generated on the fly by the server it is necessary for 
+it to be streamable. To this effect there will be no counts or sizes that
+refer to anything outside of the current record. This assures that the 
+generator will be able to build a file list without negligable server side 
+overhead. Furthermore a focus is placed on making things as small as possible,
+to this end usefull items like record length indicators are omitted. This 
+does necessarily limit the ability to handle format changes.
+                                                                  <!-- }}} -->
+
+<chapt>Structure
+<!-- Data Stream                                                      {{{ -->
+<!-- ===================================================================== -->
+<sect>Data Stream
+<p>
+The data stream is encoded as a series of variable length numbers, fixed 
+length numbers and strings. The use of variable length number encoding
+was chosen to accomidate sites with over 100000 files, mostly below 16k,
+using variable length encoding will save approximately 400k of data and still
+allow some items that are very large.
+
+<p> 
+Numbers are coded as a series of bytes of non-fixed length, the highest bit
+of each byte is 1 if the next byte is part of this number. Bytes are ordered
+backwards from the least significant to the most significant inorder to 
+simplify decoding, any omitted bits can be assumed to be 0. Clients should
+decode into their largest type and fatally error if a number expands to
+larger than that. All numbers are positive.
+
+<p>
+Strings are coded in pascal form, with a length number preceeding a series
+of 8 bit characters making up the string. The strings are coded in UTF.
+
+<p>
+The first records in the file should be a header record followed by any
+include/exclude records to indicate how the list was generated. Following
+that is the actual file list data.
+
+<p>
+The records all have the same form, they start with an 8 bit tag value and
+then have the raw record data after. The main header has a set of flags for
+all of the records types, these flags are used to designate optional portions
+of the record. For instance a 
+file record may not have a md5 hash or uid/gid values, those would be marked 
+off in the flags. Generally every non-critical value is optional. The records 
+and their tags are as follows:
+
+<list>
+<item> 0 - Header
+<item> 1 - Directory Marker
+<item> 2 - Directory Start
+<item> 3 - Directory End
+<item> 4 - Normal File
+<item> 5 - Symlink
+<item> 6 - Device Special
+<item> 7 - Directory
+<item> 8 - Include/Exclude
+<item> 9 - User Map
+<item> 10 - Group Map
+<item> 11 - Hard Link
+<item> 12 - End Marker
+<item> 13 - RSync Checksums
+<item> 14 - Aggregate File
+<item> 15 - RSync End
+</list>
+
+<p>
+The header record is placed first in the file followed by Directory records
+and then by a number of file type records. The Directory Start/End are used 
+to indicate which directory the file records are in. The approach is to 
+create a bundle of file type records for each directory that are stored
+non-recursively. The directory marker records are used with depth-first
+traversal to create unseen directories with the proper permissions.
+                                                                 <!-- }}} -->
+<!-- Header                                                           {{{ -->
+<!-- ===================================================================== -->
+<sect>Header
+<p>
+The header is the first record in the file and contains some information about
+what will follow.
+<example>
+   struct Header
+   {
+      uint8 Tag;             // 0 for the header
+      
+      uint32 Signature;
+      uint16 MajorVersion;
+      uint16 MinorVersion;
+      number Epoch;
+
+      uint8 FlagCount;
+      uint32 Flags[12];
+   };
+</example>
+<taglist>
+<tag>Signature<item>
+This field should contain the hex value 0x97E78AB which designates the file
+as a DSync file list. Like all numbers it should be stored in network byte
+order.
+
+<tag>MajorVersion
+<tag>MinorVersion<item>
+These two fields designate the revision of the format. The major version
+should be increased if an incompatible change is made to the structure of
+the file, otherwise the minor version should reflect any changes. The current
+major/minor is 0 and 0. Compatibility issues are discussed later on.
+
+<tag>Epoch<item>
+Inorder to encode time in a single 32 bit signed integer the format uses a 
+shifting epoch. Epoch is set to a time in seconds from the unix
+epoch. All other times are relative to this time.
+In this way we can specify any date 68 years in either direction from any 
+possible time. Doing so allows us to encode time using only 32 bits. The
+generator should either error or truncate if a time value exceeds this 
+representation. This does impose the limitation that the difference between
+the lowest stored date and highest stored date must be no more than 136 years.
+
+<tag>FlagCount<item>
+This designates the number of items in the flag array.
+
+<tag>Flags<item>
+Each possible record type has a flag value that is used to indicate what
+items the generator emitted. There is no per-record flag in order to save
+space. The flag array is indexed by the record ID.
+
+</taglist>
+                                                                 <!-- }}} -->
+<!-- Directory Marker                                                 {{{ -->
+<!-- ===================================================================== -->
+<sect>Directory Marker, Directory Start and Directory
+<p>
+The purpose of the directory marker record is to specify directories that
+must be created before a directory start record can be processed. It is needed
+to ensure the correct permissions and ownership are generated while the
+contents are in transfer.
+
+<p>
+A Directory Start record serves to indicate a change of directory. All further
+file type records will refer to the named directory until a Directory End
+record is processed marking the final modification for this directory. It is
+not possible to nest directory start directives, in fact a Directory Start
+record implies a Directory End record for the previosly Started Directory
+
+<p>
+The plain directory record is a file type record that refers to a directory
+file type. All of these record types describe the same thing used in different
+contexts so share the same structure.
+
+<example>
+   struct DirMarker
+   {
+      uint8 Tag;             // 1, 2 or 7 for the header
+      
+      uint32 ModTime;
+      uint16 Permissions;
+      number User;
+      number Group;
+      string Path;
+   };
+</example>
+<taglist>
+<tag>Flags [from the header]<item>
+Optional portions of the structure are Permissions (1&lt;&lt;0) and user/group
+(1&lt;&lt;1). The bit is set to 1 if they are present.
+
+<tag>ModTime<item>
+This is the number of seconds since the file list epoch, it is the modification
+date of the directory.
+
+<tag>Permissions<item>
+This is the standard unix permissions in the usual format.
+
+<tag>User
+<tag>Group<item>
+These are the standard unix user/group for the directory. They are indirected
+through the user/group maps described later on.
+
+<tag>Path<item>
+The path from the base of the file list to the directory this record describes.
+However ordinary directory types have a single name relative to the last
+Directory Start record.
+</taglist>
+                                                                 <!-- }}} -->
+<!-- Directory End                                                    {{{ -->
+<!-- ===================================================================== -->
+<sect>Directory End
+<p>
+The purpose of the directory end marker is to signafy that their will be no 
+more file type records from this directory. Directory Start and Directory
+End records must be paired. The intent of this record is to allow future 
+expansion, NOT to allow recursive directory blocks. A Directory Start
+record will imply a Directory End record if the previous was not terminated.
+
+<p>
+There are no data members, it is the basic 1 item record. If the data stream
+terminates with an open directory block it is assumed to be truncated and
+an error issued.
+
+                                                                 <!-- }}} -->
+<!-- Normal File                                                      {{{ -->
+<!-- ===================================================================== -->
+<sect>Normal File
+<p>
+A normal file is a simple, regular file. It has the standard set of unix 
+attributes and an optional MD5 hash for integrity checking.
+<example>
+   struct NormalFile
+   {
+      uint8 Tag;             // 4
+      
+      uint32 ModTime;
+      uint16 Permissions;
+      number User;
+      number Group;
+      string Name;
+      number Size;
+      uint128 MD5;
+   };
+</example>
+<taglist>
+<tag>Flags [from the header]<item>
+Optional portions of the structure are Permissions (1&lt;&lt;0), user/group
+(1&lt;&lt;1), and MD5 (1&lt;&lt;2). The bit is set to 1 if they are present.
+
+<tag>ModTime<item>
+This is the number of seconds since the file list epoch, it is the modification
+date of the file.
+
+<tag>Permissions<item>
+This is the standard unix permissions in the usual format.
+
+<tag>User
+<tag>Group<item>
+These are the standard unix user/group for the directory. They are indirected
+through the user/group maps described later on. 
+
+<tag>Name<item>
+The name of the item. It should have no pathname components and is relative
+to the last Directory Start record.
+
+<tag>MD5<item>
+This is a MD5 hash of the file.
+
+<tag>Size<item>
+This is the size of the file in bytes.
+</taglist>
+                                                                 <!-- }}} -->
+<!-- Symlink                                                          {{{ -->
+<!-- ===================================================================== -->
+<sect>Symlink
+<p>
+This encodes a normal unix symbolic link. Symlinks do not have permissions
+or size, but do have optional ownership.
+<example>
+   struct Symlink
+   {
+      uint8 Tag;             // 5
+
+      uint32 ModTime;
+      number User;
+      number Group;
+      string Name;
+      uint8 Compression;
+      string To;
+   };
+</example>
+<taglist>
+<tag>Flags [from the header]<item>
+Optional portions of the structure are, user/group
+(1&lt;&lt;0). The bit is set to 1 if they are present.
+
+<tag>ModTime<item>
+This is the number of seconds since the file list epoch, it is the modification
+date of the file.
+
+<tag>User
+<tag>Group<item>
+These are the standard unix user/group for the directory. They are indirected
+through the user/group maps described later on. 
+
+<tag>Name<item>
+The name of the item. It should have no pathname components and is relative
+to the last Directory Start record.
+
+<tag>Compression<item>
+Common use of symlinks makes them very easy to compress, the compression
+byte allows this. It is an 8 bit byte with the first 7 bits representing an 
+unsigned number and the 8th bit as being a flag. The first 7 bits describe
+how many bytes of the last symlink should be prepended to To and if the 8th 
+bit is set then Name is appended to To.
+
+<tag>To<item>
+This is the file the symlink is pointing to. It is an absolute string taken
+as is. The client may perform checking on it if desired. The string is 
+compressed as described in the Compression field.
+</taglist>
+                                                                  <!-- }}} -->
+<!-- Device Special                                                   {{{ -->
+<!-- ===================================================================== -->
+<sect>Device Special
+<p>
+Device Special records encode unix device special files, which have a major
+and a minor number corrisponding to some OS specific attribute. These also
+encode fifo files, anything that can be created by mknod.
+<example>
+   struct DeviceSpecial
+   {
+      uint8 Tag;             // 6
+      
+      uint32 ModTime;
+      uint16 Permissions;
+      number User;
+      number Group;
+      number Dev;
+      string Name;
+   };
+</example>
+<taglist>
+<tag>Flags [from the header]<item>
+Optional portions of the structure areuser/group
+(1&lt;&lt;0). The bit is set to 1 if they are present.
+
+<tag>ModTime<item>
+This is the number of seconds since the file list epoch, it is the modification
+date of the file.
+
+<tag>Permissions<item>
+This non-optional field is used to encode the type of device and the 
+creation permissions.
+
+<tag>Dev<item>
+This is the OS specific 'dev_t' field for mknod.
+
+<tag>Major
+<tag>Minor<item>
+These are the OS dependent device numbers.
+
+<tag>Name<item>
+The name of the item. It should have no pathname components and is relative
+to the last Directory Start record.
+
+<tag>To<item>
+This is the file the symlink is pointing to.
+</taglist>
+                                                                  <!-- }}} -->
+<!-- Include/Exclude                                                  {{{ -->
+<!-- ===================================================================== -->
+<sect>Include and Exclude
+<p>
+The include/exclude list used to generate the file list is encoded after
+the header record. It is stored as an ordered set of include/exclude records
+acting as a filter. If no record matches then the pathname is assumed to 
+be included otherwise the first matching record decides.
+
+<example>
+   struct IncludeExclude
+   {
+      uint8 Tag;             // 8
+      
+      uint8 Type;
+      string Pattern;
+   };
+</example>
+<taglist>
+<tag>Flags [from the header]<item>
+None defined.
+
+<tag>Type<item>
+This is the sort of rule, presently 1 is an include rule and 2 is an exclude
+rule.
+
+<tag>Pattern<item>
+This is the textual pattern used for matching.
+
+</taglist>
+                                                                  <!-- }}} -->
+<!-- User/Group Map                                                   {{{ -->
+<!-- ===================================================================== -->
+<sect>User/Group Map
+<p>
+In order to properly transfer users and groups the names are converted from
+a local number into a file list number and a number to name mapping. When
+the remote side reads the file list it directs all UID/GID translations
+through the mapping to create the real names and then does a local lookup.
+This also provides some compressesion in the file list as large UIDs are
+converted into smaller values through the mapping.
+
+<p>
+The generator is expected to emit these records at any place before the IDs
+are actually used.
+<example>
+   struct NameMap
+   {
+      uint8 Tag;             // 9,10
+      
+      number FileID;
+      number RealID;
+      string Name;
+   };
+</example>
+<taglist>
+<tag>Flags [from the header]<item>
+Optional portions of the structure are RealID (1&lt;&lt;0).
+
+<tag>FileID<item>
+This is the ID used internally in the file list, it should be monotonically
+increasing each time a Map record is created so that it is small and unique.
+
+<tag>RealID<item>
+This is the ID used in the filesystem on the generating end. This information
+maybe used if the user selected to regenerate IDs without translation.
+</taglist>
+                                                                  <!-- }}} -->
+<!-- Hard Link                                                        {{{ -->
+<!-- ===================================================================== -->
+<sect>Hard Link
+<p>
+A hard link record is used to record a file that is participating in a hard
+link. The only information we know about the link is the inode and device 
+on the local machine, so we store this information. The client will have to
+reconstruct the linkages if possible.
+
+<example>
+   struct HardLink
+   {
+      uint8 Tag;             // 11
+      
+      uint32 ModTime;
+      number Serial;
+      uint16 Permissions;
+      number User;
+      number Group;
+      string Name;
+      number Size;
+      uint128 MD5;
+   };
+</example>
+<taglist>
+<tag>Flags [from the header]<item>
+Optional portions of the structure are Permissions (1&lt;&lt;0), user/group
+(1&lt;&lt;1), and MD5 (1&lt;&lt;2). The bit is set to 1 if they are present.
+
+<tag>ModTime<item>
+This is the number of seconds since the file list epoch, it is the modification
+date of the file.
+
+<tag>Serial<item>
+This is the unique ID number for the hardlink. It is composed from the
+device inode pair in a generator dependent way. The exact nature of the
+value is unimportant, only that two hard link records with the same serial
+should be linked together. It is recommended that the generator compress
+hard link serial numbers into small monotonically increasing IDs.
+
+<tag>Permissions<item>
+This is the standard unix permissions in the usual format.
+
+<tag>User
+<tag>Group<item>
+These are the standard unix user/group for the directory. They are indirected
+through the user/group maps described later on. 
+
+<tag>Name<item>
+The name of the item. It should have no pathname components and is relative
+to the last Directory Start record.
+
+<tag>MD5<item>
+This is a MD5 hash of the file.
+
+<tag>Size<item>
+This is the size of the file in bytes.
+</taglist>
+                                                                 <!-- }}} -->
+<!-- End Marker                                                               {{{ -->
+<!-- ===================================================================== -->
+<sect>End Marker
+<p>
+The End Marker is the final record in the stream, if it is missing the stream
+is assumed to be incomplete.
+<example>
+   struct Trailer
+   {
+      uint8 Tag;             // 12 for the header
+      
+      uint32 Signature;
+   };
+</example>
+<taglist>
+<tag>Signature<item>
+This field should contain the hex value 0xBA87E79 which is designed to 
+prevent a correputed stream as begin a legitimate end marker.
+</taglist>
+                                                                 <!-- }}} -->
+
+<!-- RSync Checksums                                                  {{{ -->
+<!-- ===================================================================== -->
+<sect>RSync Checksums
+<p>
+The checksum record contains the list of checksums for a file and represents
+the start of a RSync description block which may contain RSync Checksums,
+a Normal File entry or Aggregate Files records.
+<example>
+   struct RSyncChecksums
+   {
+      uint8 Tag;             // 13
+      
+      number BlockSize;
+      number FileSize;
+      uint160 Sums[ceil(FileSize/BlockSize)];
+   };
+</example>
+<taglist>
+<tag>BlockSize<item>
+The size of each block in the stream in bytes.
+
+<tag>FileSize<item>
+The total size of the the file in bytes.
+
+<tag>Sums<item>
+The actual checksum data. The format has the lower 32 bytes as the weak 
+checksum and the upper 128 as the strong checksum.
+</taglist>
+                                                                 <!-- }}} -->
+<!-- Aggregate File                                                   {{{ -->
+<!-- ===================================================================== -->
+<sect>Aggregate File
+<p>
+If the generator was given a list of included files this record will be 
+emitted after the rsync checksum record, once for each file. The given 
+paths are files that are likely to contain fragments of the larger file.
+<example>
+   struct AggregateFile
+   {
+      uint8 Tag;             // 14 for this record
+      
+      string File;
+   };
+</example>
+<taglist>
+<tag>File<item>
+The stored filename.
+</taglist>
+                                                                 <!-- }}} -->
+<!-- RSync End                                                        {{{ -->
+<!-- ===================================================================== -->
+<sect>RSync End
+<p>
+The purpose of the directory end marker is to signafy that the RSync data
+is finished. RSync blocks begin with the RSync checksum record, then are
+typically followed by a Normal File record describing the name and attributes
+of the file and then optionally followed by a set of Aggregate File records.
+
+<p>
+There are no data members, it is the basic 1 item record. If the data stream
+terminates with an open block it is assumed to be truncated and an error 
+issued.
+                                                                 <!-- }}} -->
+
+<chapt>The Client
+<!-- Handling Compatibility                                            {{{ -->
+<!-- ===================================================================== -->
+<sect>Handling Compatibility
+<p>
+The format has no provision for making backwards compatible changes, even
+minor ones. What was provided is a way to make a generator that is both
+forwards and backwards compatible with clients, this is done by disabling
+generation of unsupported items and masking them off in the flags.
+
+<p>
+To deal with this a client should examine the header and determine if it has
+a suitable major version, the minor version should largely be ignored. The
+client should then examine the flags values and for all records it understands
+ensure that no bits are masked on that it does not understand. Records that
+it cannot handle should be ignored at this point. When the client is
+parsing it should abort if it hits a record it does not support.
+                                                                 <!-- }}} -->
+<!-- Client Requirements                                              {{{ -->
+<!-- ===================================================================== -->
+<sect>Client Requirements
+<p>
+The client attempting to verify syncronisity of a local file tree and a
+tree destribed in a file list must do three things, look for extra local files,
+manage the UID/GID mappings and maintain a map of hardlinks. These items 
+corrispond to the only necessary memory usage on the client.
+
+<p>
+It is expected that the client will use the timestamp, size and possibly
+MD5 hash to match the local file against the remote one to decide if it 
+should be retrieved.
+
+<p>
+Hardlinks are difficult to handle, but represent a very usefull feature. The
+client should track all hard links until they are associated with a local
+file+inode, then all future links to that remote inode can be recreated 
+locally.
+                                                                 <!-- }}} -->
+
+<chapt>RSync Method
+<!-- Overview                                                          {{{ -->
+<!-- ===================================================================== -->
+<sect>Overview
+<p>
+The <em>rsync method</> was invented by Andrew Tridgell and originally 
+implemented in the rsync program. DSync has a provision to make use of the 
+<em>rsync method</> for transfering differences between files effeciently,
+however the implemention is not as bandwidth efficient as what the rsync 
+program uses, emphasis is placed on generator efficiency. 
+
+<p>
+Primarily the <em>rsync method</> makes use of a series of weak and strong
+block checksums for each block in a file. Blocks are a uniform size and
+are uniformly distributed about the source file. In order to minimize server
+loading the checksum data is generated for the file on the server and then 
+sent to the client - this might optionally be done from a cached file. The
+client is responsible for performing the checksumming and searching on its
+end.
+
+<p>
+In contrast rsync has the client send its checksums to the server and the
+server sends back commands to reconstruct the file. This is more bandwidth
+efficient because only one round trip is required and there is a higher chance
+that more blocks will be matched and not need to be sent to the client.
+
+<p>
+Furthermore a feature designed for use by CD images is provided where a file
+can be specified as the aggregation of many smaller files. The aggregated
+files are specified only by giving the file name. The client is expected to 
+read the file (probably from the network) and perform checksum searching 
+against the provided table.
+
+                                                                 <!-- }}} -->
+<!-- CD Images                                                         {{{ -->
+<!-- ===================================================================== -->
+<sect>CD Images
+<p>
+The primary and most complex use of the rsync data is for forming CD images
+on the fly from a mirror and a CD source. This is extremly usefull beacause 
+CD images take up alot of space and bandwidth to mirror, while they are 
+mearly aggregates of (possibly) already mirrored data. Using checksums
+and a file listing allows the CD image to be reconstructed from any mirror
+and reduces the loading on primary CD image servers.
+
+<p>
+The next use of checksums is to 'freshen' a CD image during development. If
+a image is already present that contains a subset of the required data the 
+checksums generally allow a large percentage of that data to be reused.
+
+<p>
+Since the client is responsible for reconstruction and checksum searching it
+is possible to perform in place reconstruction and in place initial generation
+that does not require a (large!) temporary file.
+                                                                 <!-- }}} -->
+
+
+</book>
diff --git a/tools/dsync-0.0/doc/makefile b/tools/dsync-0.0/doc/makefile
new file mode 100644 (file)
index 0000000..d2130df
--- /dev/null
@@ -0,0 +1,14 @@
+# -*- make -*-
+BASE=..
+SUBDIR=doc
+
+# Bring in the default rules
+include ../buildlib/defaults.mak
+
+# SGML Documents
+SOURCE = filelist.sgml
+include $(DEBIANDOC_H)
+
+# Man pages
+SOURCE = dsync-flist.1
+include $(YODL_MANPAGE_H)
diff --git a/tools/dsync-0.0/libdsync/compare.cc b/tools/dsync-0.0/libdsync/compare.cc
new file mode 100644 (file)
index 0000000..95a286b
--- /dev/null
@@ -0,0 +1,608 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: compare.cc,v 1.6 1999/12/26 06:59:00 jgg Exp $
+/* ######################################################################
+   
+   Compare a file list with a local directory
+   
+   The first step in the compare is to read the names of each entry
+   in the local directory into ram. This list is the first step to
+   creating a delete list. Next we begin scanning the file list, checking
+   each entry against the dir contents, if a match is found it is removed
+   from the dir list and then stat'd to verify against the file list
+   contents. If no match is found then the entry is marked for download.
+   When done the local directory in ram will only contain entries that 
+   need to be erased.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/compare.h"
+#endif
+
+#include <dsync/compare.h>
+#include <dsync/error.h>
+#include <dsync/fileutl.h>
+#include <dsync/md5.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <utime.h>
+#include <stdio.h>
+                                                                       /*}}}*/
+
+// DirCompre::dsDirCompare - Constructor                               /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsDirCompare::dsDirCompare() : IndexSize(0), IndexAlloc(0), Indexes(0),
+                     NameAlloc(0), Names(0), Verify(true), HashLevel(Md5Date)
+{
+   IndexAlloc = 1000;
+   Indexes = (unsigned int *)malloc(sizeof(*Indexes)*IndexAlloc);
+   NameAlloc = 4096*5;
+   Names = (char *)malloc(sizeof(*Names)*NameAlloc);
+   if (Names == 0 || Indexes == 0)
+      _error->Error("Cannot allocate memory");
+}
+                                                                       /*}}}*/
+// DirCompare::~dsDirCompare - Destructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsDirCompare::~dsDirCompare()
+{
+   free(Names);
+   free(Indexes);
+}
+                                                                       /*}}}*/
+// DirCompare::LoadDir - Load all the names in the directory           /*{{{*/
+// ---------------------------------------------------------------------
+/* Such in every name in the directory, we store them as a packed, indexed
+   array of strings */
+bool dsDirCompare::LoadDir()
+{
+   // Scan the directory
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+      return _error->Errno("opendir","Unable to open directory %s",SafeGetCWD().c_str());
+   struct dirent *Ent;
+   IndexSize = 0;
+   char *End = Names + 1;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+      
+      // Grab some more bytes in the name allocation
+      if ((unsigned)(NameAlloc - (End - Names)) <= strlen(Ent->d_name)+1)
+      {
+        unsigned long OldEnd = End - Names;
+        char *New = (char *)realloc(Names,sizeof(*Names)*NameAlloc + 4*4096);
+        if (New == 0)
+        {
+           closedir(DirSt);
+           return _error->Error("Cannot allocate memory");
+        }
+        
+        Names = New;
+        NameAlloc += 4*4096;
+        End = Names + OldEnd;
+      }
+      
+      // Grab some more bytes in the index allocation
+      if (IndexSize >= IndexAlloc)
+      {
+        unsigned int *New = (unsigned int *)realloc(Indexes,
+                            sizeof(*Indexes)*IndexAlloc + 1000);
+        if (New == 0)
+        {   
+           closedir(DirSt);
+           return _error->Error("Cannot allocate memory");
+        }
+        
+        Indexes = New;
+        IndexAlloc += 4*4096;
+      }
+      
+      // Store it
+      Indexes[IndexSize] = End - Names;
+      IndexSize++;
+      strcpy(End,Ent->d_name);
+      End += strlen(End) + 1;
+   }
+   
+   closedir(DirSt);
+   return true;
+}
+                                                                       /*}}}*/
+// DirCompare::Process - Process the file list stream                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This scans over the dirs from the IO and decides what to do with them */
+bool dsDirCompare::Process(string Base,dsFList::IO &IO)
+{
+   // Setup the queues and store the current directory
+   string StartDir = SafeGetCWD();
+   
+   // Change to the base directory
+   if (chdir(Base.c_str()) != 0)
+      return _error->Errno("chdir","Could not change to %s",Base.c_str());
+   Base = SafeGetCWD();
+   this->Base = Base;
+   
+   string CurDir;
+   dsFList List;
+   bool Missing = false;
+   while (List.Step(IO) == true)
+   {
+      if (Visit(List,CurDir) == false)
+        return false;
+      
+      switch (List.Tag)
+      {
+        // Handle a forward directory reference
+        case dsFList::tDirMarker:
+        {
+           // Ingore the root directory
+           if (List.Entity->Name.empty() == true)
+              continue;
+           
+           char S[1024];
+
+           snprintf(S,sizeof(S),"%s%s",Base.c_str(),List.Entity->Name.c_str());
+           
+           /* We change the path to be absolute for the benifit of the 
+              routines below */
+           List.Entity->Name = S;
+           
+           // Stat the marker dir
+           struct stat St;
+           bool Res;
+           if (lstat(S,&St) != 0)
+              Res = Fetch(List,string(),0);
+           else
+              Res = Fetch(List,string(),&St);
+
+           if (Res == false)
+              return false;
+           break;
+        }
+        
+        // Start a directory
+        case dsFList::tDirStart:
+        {          
+           if (DoDelete(CurDir) == false)
+              return false;
+           if (chdir(Base.c_str()) != 0)
+              return _error->Errno("chdir","Could not change to %s",Base.c_str());
+           
+           CurDir = List.Dir.Name;
+           Missing = false;
+           IndexSize = 0;
+           if (List.Dir.Name.empty() == false)
+           {
+              /* Instead of erroring out we just mark them as missing and
+                 do not re-stat. This is to support the verify mode, the
+                 actual downloader should never get this. */
+              if (chdir(List.Dir.Name.c_str()) != 0)
+              {
+                 if (Verify == false)
+                    return _error->Errno("chdir","Unable to cd to %s%s.",Base.c_str(),List.Dir.Name.c_str());
+                 Missing = true;
+              }               
+           }
+           
+           if (Missing == false)
+              LoadDir();
+           break;
+        }
+        
+        // Finalize the directory
+        case dsFList::tDirEnd:
+        {
+           if (DoDelete(CurDir) == false)
+              return false;
+           IndexSize = 0;
+           if (chdir(Base.c_str()) != 0)
+              return _error->Errno("chdir","Could not change to %s",Base.c_str());
+           break;
+        }       
+      }
+      
+      // We have some sort of normal entity
+      if (List.Entity != 0 && List.Tag != dsFList::tDirMarker &&
+         List.Tag != dsFList::tDirStart)
+      {
+        // See if it exists, if it does then stat it
+        bool Res = true;
+        if (Missing == true || DirExists(List.Entity->Name) == false)
+           Res = Fetch(List,CurDir,0);
+        else
+        {
+           struct stat St;
+           if (lstat(List.Entity->Name.c_str(),&St) != 0)
+              Res = Fetch(List,CurDir,0);
+           else
+              Res = Fetch(List,CurDir,&St);
+        }
+        if (Res == false)
+           return false;
+      }
+      
+      // Fini
+      if (List.Tag == dsFList::tTrailer)
+      {
+        if (DoDelete(CurDir) == false)
+           return false;
+        return true;
+      }      
+   }
+   
+   return false;
+}
+                                                                       /*}}}*/
+// DirCompare::DoDelete - Delete files in the delete list              /*{{{*/
+// ---------------------------------------------------------------------
+/* The delete list is created by removing names that were found till only
+   extra names remain */
+bool dsDirCompare::DoDelete(string Dir)
+{
+   for (unsigned int I = 0; I != IndexSize; I++)
+   {
+      if (Indexes[I] == 0)
+        continue;
+      if (Delete(Dir,Names + Indexes[I]) == false)
+        return false;
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCompare::Fetch - Fetch an entity                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This examins an entry to see what sort of fetch should be done. There
+   are three sorts, 
+     New - There is no existing data
+     Changed - There is existing data
+     Meta - The data is fine but the timestamp/owner/perms might not be */
+bool dsDirCompare::Fetch(dsFList &List,string Dir,struct stat *St)
+{
+   if (List.Tag != dsFList::tNormalFile && List.Tag != dsFList::tDirectory &&
+       List.Tag != dsFList::tSymlink && List.Tag != dsFList::tDeviceSpecial &&
+       List.Tag != dsFList::tDirMarker)
+      return _error->Error("dsDirCompare::Fetch called for an entity "
+                          "that it does not understand");
+   
+   // This is a new entitiy
+   if (St == 0)
+      return GetNew(List,Dir);
+
+   /* Check the types for a mis-match, if they do not match then 
+      we have to erase the entity and get a new one */
+   if ((S_ISREG(St->st_mode) != 0 && List.Tag != dsFList::tNormalFile) ||
+       (S_ISDIR(St->st_mode) != 0 && (List.Tag != dsFList::tDirectory && 
+                                     List.Tag != dsFList::tDirMarker)) ||
+       (S_ISLNK(St->st_mode) != 0 && List.Tag != dsFList::tSymlink) ||
+       ((S_ISCHR(St->st_mode) != 0 || S_ISBLK(St->st_mode) != 0 || 
+        S_ISFIFO(St->st_mode) != 0) && List.Tag != dsFList::tDeviceSpecial))
+   {
+      return Delete(Dir,List.Entity->Name.c_str(),true) && GetNew(List,Dir);
+   }
+   
+   // First we check permissions and mod time
+   bool ModTime = (signed)(List.Entity->ModTime + List.Head.Epoch) == St->st_mtime;
+   bool Perm = true;
+   if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+      Perm = List.Entity->Permissions == (unsigned)(St->st_mode & ~S_IFMT);
+      
+   // Normal file
+   if (List.Tag == dsFList::tNormalFile)
+   {
+      // Size mismatch is an immedate fail
+      if (List.NFile.Size != (unsigned)St->st_size)
+        return GetChanged(List,Dir);
+
+      // Try to check the stored MD5
+      if (HashLevel == Md5Always || 
+         (HashLevel == Md5Date && ModTime == false))
+      {
+        if ((List.Head.Flags[List.Tag] & dsFList::NormalFile::FlMD5) != 0)
+        {
+           if (CheckHash(List,Dir,List.NFile.MD5) == true)
+              return FixMeta(List,Dir,*St);
+           else
+              return GetChanged(List,Dir);
+        }       
+      }
+      
+      // Look at the modification time
+      if (ModTime == true)
+        return FixMeta(List,Dir,*St);
+      return GetChanged(List,Dir);
+   }
+
+   // Check symlinks
+   if (List.Tag == dsFList::tSymlink)
+   {
+      char Buf[1024];
+      int Res = readlink(List.Entity->Name.c_str(),Buf,sizeof(Buf));
+      if (Res > 0)
+        Buf[Res] = 0;
+      
+      // Link is invalid
+      if (Res < 0 || List.SLink.To != Buf)
+        return GetNew(List,Dir);
+      
+      return FixMeta(List,Dir,*St);
+   }
+
+   // Check directories and dev special files
+   if (List.Tag == dsFList::tDirectory || List.Tag == dsFList::tDeviceSpecial ||
+       List.Tag == dsFList::tDirMarker)
+      return FixMeta(List,Dir,*St);
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCompare::DirExists - See if the entry exists in our dir table    /*{{{*/
+// ---------------------------------------------------------------------
+/* We look at the dir table for one that exists */
+bool dsDirCompare::DirExists(string Name)
+{
+   for (unsigned int I = 0; I != IndexSize; I++)
+   {
+      if (Indexes[I] == 0)
+        continue;
+      if (Name == Names + Indexes[I])
+      {
+        Indexes[I] = 0;
+        return true;
+      }
+   }
+   return false;
+}
+                                                                       /*}}}*/
+// DirCompare::CheckHash - Check the MD5 of a entity                   /*{{{*/
+// ---------------------------------------------------------------------
+/* This is invoked to see of the local file we have is the file the remote
+   says we should have. */
+bool dsDirCompare::CheckHash(dsFList &List,string Dir,unsigned char MD5[16])
+{
+   // Open the file
+   MD5Summation Sum;
+   FileFd Fd(List.Entity->Name,FileFd::ReadOnly);
+   if (_error->PendingError() == true)
+      return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
+                          List.Entity->Name.c_str());
+
+   if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
+      return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
+                          List.Entity->Name.c_str());
+
+   unsigned char MyMD5[16];
+   Sum.Result().Value(MyMD5);
+
+   return memcmp(MD5,MyMD5,sizeof(MyMD5)) == 0;
+}
+                                                                       /*}}}*/
+// DirCompare::FixMeta - Fix timestamps, ownership and permissions     /*{{{*/
+// ---------------------------------------------------------------------
+/* This checks if it is necessary to correct the timestamps, ownership and
+   permissions of an entity */
+bool dsDirCompare::FixMeta(dsFList &List,string Dir,struct stat &St)
+{   
+   // Check the mod time
+   if (List.Tag != dsFList::tSymlink)
+   {
+      if ((signed)(List.Entity->ModTime + List.Head.Epoch) != St.st_mtime)
+        if (SetTime(List,Dir) == false)
+           return false;
+   
+      // Check the permissions
+      if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+      {
+        if (List.Entity->Permissions != (St.st_mode & ~S_IFMT))
+           if (SetPerm(List,Dir) == false)
+              return false;
+      }
+   }
+      
+   return true;
+}
+                                                                       /*}}}*/
+
+// DirCorrect::GetNew - Create a new entry                             /*{{{*/
+// ---------------------------------------------------------------------
+/* We cannot create files but we do generate everything else. */
+bool dsDirCorrect::GetNew(dsFList &List,string Dir)
+{
+   if (List.Tag == dsFList::tDirectory)
+   {
+      unsigned long PermDir = 0666;
+      if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+        PermDir = List.Entity->Permissions;
+        
+      if (mkdir(List.Entity->Name.c_str(),PermDir) != 0)
+        return _error->Errno("mkdir","Unable to create directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      // Stat the newly created file for FixMeta's benifit
+      struct stat St;
+      if (lstat(List.Entity->Name.c_str(),&St) != 0)
+        return _error->Errno("stat","Unable to stat directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      return FixMeta(List,Dir,St);
+   }
+
+   if (List.Tag == dsFList::tSymlink)
+   {
+      if (symlink(List.SLink.To.c_str(),List.Entity->Name.c_str()) != 0)
+        return _error->Errno("symlink","Unable to create symlink, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      // Stat the newly created file for FixMeta's benifit
+      struct stat St;
+      if (lstat(List.Entity->Name.c_str(),&St) != 0)
+        return _error->Errno("stat","Unable to stat directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      return FixMeta(List,Dir,St);
+   }
+   
+   if (List.Tag == dsFList::tDeviceSpecial)
+   {
+      unsigned long PermDev;
+      if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
+        PermDev = List.Entity->Permissions;
+      else
+        return _error->Error("Corrupted file list");
+      
+      if (mknod(List.Entity->Name.c_str(),PermDev,List.DevSpecial.Dev) != 0)
+        return _error->Errno("mkdir","Unable to create directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+
+      // Stat the newly created file for FixMeta's benifit
+      struct stat St;
+      if (lstat(List.Entity->Name.c_str(),&St) != 0)
+        return _error->Errno("stat","Unable to stat directory, %s%s",
+                             Dir.c_str(),List.Entity->Name.c_str());
+      return FixMeta(List,Dir,St);
+   }   
+}
+                                                                       /*}}}*/
+// DirCorrect::DirUnlink - Unlink a directory                          /*{{{*/
+// ---------------------------------------------------------------------
+/* This just recursively unlinks stuff */
+bool dsDirCorrect::DirUnlink(const char *Path)
+{
+   // Record what dir we were in
+   struct stat Dir;
+   if (lstat(".",&Dir) != 0)
+      return _error->Errno("lstat","Unable to stat .!");
+
+   if (chdir(Path) != 0)
+      return _error->Errno("chdir","Unable to change to %s",Path);
+            
+   // Scan the directory
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+   {
+      chdir("..");
+      return _error->Errno("opendir","Unable to open directory %s",Path);
+   }
+   
+   // Erase this directory
+   struct dirent *Ent;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+
+      struct stat St;
+      if (lstat(Ent->d_name,&St) != 0)
+        return _error->Errno("stat","Unable to stat %s",Ent->d_name);
+      if (S_ISDIR(St.st_mode) == 0)
+      {
+        // Try to unlink the file
+        if (unlink(Ent->d_name) != 0)
+        {
+           chdir("..");
+           return _error->Errno("unlink","Unable to remove file %s",Ent->d_name);
+        }       
+      }
+      else
+      {
+        if (DirUnlink(Ent->d_name) == false)
+        {
+           chdir("..");
+           closedir(DirSt);
+           return false;
+        }       
+      }         
+   }
+   closedir(DirSt);
+   chdir("..");
+   
+   /* Make sure someone didn't screw with the directory layout while we
+      were erasing */
+   struct stat Dir2;
+   if (lstat(".",&Dir2) != 0)
+      return _error->Errno("lstat","Unable to stat .!");
+   if (Dir2.st_ino != Dir.st_ino || Dir2.st_dev != Dir.st_dev)
+      return _error->Error("Hey! Someone is fiddling with the dir tree as I erase it!");
+
+   if (rmdir(Path) != 0)
+      return _error->Errno("rmdir","Unable to remove directory %s",Ent->d_name);
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCorrect::Delete - Delete an entry                                        /*{{{*/
+// ---------------------------------------------------------------------
+/* This obliterates an entity - recursively, use with caution. */
+bool dsDirCorrect::Delete(string Dir,const char *Name,bool Now)
+{
+   struct stat St;
+   if (lstat(Name,&St) != 0)
+      return _error->Errno("stat","Unable to stat %s%s",Dir.c_str(),Name);
+      
+   if (S_ISDIR(St.st_mode) == 0)
+   {
+      if (unlink(Name) != 0)
+        return _error->Errno("unlink","Unable to remove %s%s",Dir.c_str(),Name);
+   }
+   else
+   {
+      if (DirUnlink(Name) == false)
+        return _error->Error("Unable to erase directory %s%s",Dir.c_str(),Name);
+   }
+   return true;   
+}
+                                                                       /*}}}*/
+// DirCorrect::GetChanged - Get a changed entry                                /*{{{*/
+// ---------------------------------------------------------------------
+/* This is only called for normal files, we cannot do anything here. */
+bool dsDirCorrect::GetChanged(dsFList &List,string Dir)
+{   
+   return true;
+}
+                                                                       /*}}}*/
+// DirCorrect::SetTime - Change the timestamp                          /*{{{*/
+// ---------------------------------------------------------------------
+/* This fixes the mod time of the file */
+bool dsDirCorrect::SetTime(dsFList &List,string Dir)
+{
+   struct utimbuf Time;
+   Time.actime = Time.modtime = List.Entity->ModTime + List.Head.Epoch;
+   if (utime(List.Entity->Name.c_str(),&Time) != 0)
+      return _error->Errno("utimes","Unable to change mod time for %s%s",
+                          Dir.c_str(),List.Entity->Name.c_str());
+   return true;
+}
+                                                                       /*}}}*/
+// DirCorrect::SetPerm - Change the permissions                                /*{{{*/
+// ---------------------------------------------------------------------
+/* This fixes the permissions */
+bool dsDirCorrect::SetPerm(dsFList &List,string Dir)
+{
+   if (chmod(List.Entity->Name.c_str(),List.Entity->Permissions) != 0)
+      return _error->Errno("chmod","Unable to change permissions for %s%s",
+                          Dir.c_str(),List.Entity->Name.c_str());
+   return true;
+}
+                                                                       /*}}}*/
+// Dircorrect::SetOwner - Change ownership                             /*{{{*/
+// ---------------------------------------------------------------------
+/* This fixes the file ownership */
+bool dsDirCorrect::SetOwners(dsFList &List,string Dir)
+{
+   return _error->Error("Ownership is not yet supported");
+}
+                                                                       /*}}}*/
+   
diff --git a/tools/dsync-0.0/libdsync/compare.h b/tools/dsync-0.0/libdsync/compare.h
new file mode 100644 (file)
index 0000000..547137f
--- /dev/null
@@ -0,0 +1,86 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: compare.h,v 1.3 1999/01/17 22:00:51 jgg Exp $
+/* ######################################################################
+   
+   Compare a file list with a local directory 
+   
+   The Compare class looks at the file list and then generates events
+   to cause the local directory tree to become syncronized with the 
+   remote tree.
+   
+   The Correct class takes the events and applies them to the local tree.
+   It only applies information that is stored in the file list, another
+   class will have to hook the events to actually fetch files for download.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_COMPARE
+#define DSYNC_COMPARE
+
+#ifdef __GNUG__
+#pragma interface "dsync/compare.h"
+#endif 
+
+#include <dsync/filelist.h>
+
+class dsDirCompare
+{
+   unsigned int IndexSize;
+   unsigned int IndexAlloc;
+   unsigned int *Indexes;
+   unsigned int NameAlloc;
+   char *Names;
+
+   protected:
+   // Location of the tree
+   string Base;
+   
+   // Scan helpers
+   bool LoadDir();
+   bool DoDelete(string Dir);
+   bool Fetch(dsFList &List,string Dir,struct stat *St);
+   bool DirExists(string Name);
+   virtual bool CheckHash(dsFList &List,string Dir,unsigned char MD5[16]);
+   virtual bool FixMeta(dsFList &List,string Dir,struct stat &St);
+   virtual bool Visit(dsFList &List,string Dir) {return true;};
+
+   // Derived classes can hook these to actuall make them do something
+   virtual bool GetNew(dsFList &List,string Dir) {return true;};
+   virtual bool Delete(string Dir,const char *Name,bool Now = false) {return true;};
+   virtual bool GetChanged(dsFList &List,string Dir) {return true;};
+   virtual bool SetTime(dsFList &List,string Dir) {return true;};
+   virtual bool SetPerm(dsFList &List,string Dir) {return true;};
+   virtual bool SetOwners(dsFList &List,string Dir) {return true;};
+   
+   public:
+
+   bool Verify;
+   enum {Md5Never, Md5Date, Md5Always} HashLevel;
+   
+   bool Process(string Base,dsFList::IO &IO);
+   
+   dsDirCompare();
+   virtual ~dsDirCompare();
+};
+
+class dsDirCorrect : public dsDirCompare
+{
+   bool DirUnlink(const char *Path);
+      
+   protected:
+
+   // Derived classes can hook these to actuall make them do something
+   virtual bool GetNew(dsFList &List,string Dir);
+   virtual bool Delete(string Dir,const char *Name,bool Now = false);
+   virtual bool GetChanged(dsFList &List,string Dir);
+   virtual bool SetTime(dsFList &List,string Dir);
+   virtual bool SetPerm(dsFList &List,string Dir);
+   virtual bool SetOwners(dsFList &List,string Dir);
+   
+   public:
+   
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/bitmap.cc b/tools/dsync-0.0/libdsync/contrib/bitmap.cc
new file mode 100644 (file)
index 0000000..87d87b7
--- /dev/null
@@ -0,0 +1,40 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: bitmap.cc,v 1.1 1999/11/05 05:47:06 jgg Exp $
+/* ######################################################################
+   
+   Bitmap - A trivial class to implement an 1 bit per element boolean
+            vector
+   
+   This is deliberately extremely light weight so that it is fast for 
+   the client.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/bitmap.h"
+#endif
+
+#include <dsync/bitmap.h>
+
+#include <string.h>
+                                                                       /*}}}*/
+
+// BitmapVector::BitmapVector - Constructor                            /*{{{*/
+// ---------------------------------------------------------------------
+/* Allocate just enough bytes and 0 it */
+BitmapVector::BitmapVector(unsigned long Size) : Size(Size)
+{
+   Vect = new unsigned long[Bytes()];
+   memset(Vect,0,Bytes());
+}
+                                                                       /*}}}*/
+// BitmapVector::~BitmapVector - Destructor                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+BitmapVector::~BitmapVector()
+{
+   delete [] Vect;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/bitmap.h b/tools/dsync-0.0/libdsync/contrib/bitmap.h
new file mode 100644 (file)
index 0000000..9859673
--- /dev/null
@@ -0,0 +1,49 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: bitmap.h,v 1.1 1999/11/05 05:47:06 jgg Exp $
+/* ######################################################################
+
+   Bitmap - A trivial class to implement an 1 bit per element boolean
+            vector
+   
+   This is deliberately extremely light weight so that it is fast for 
+   the client.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_BITMAP
+#define DSYNC_BITMAP
+
+#ifdef __GNUG__
+#pragma interface "dsync/bitmap.h"
+#endif 
+
+class BitmapVector
+{
+   unsigned long *Vect;
+   unsigned long Size;
+   
+   #define BITMAPVECTOR_SIZE sizeof(unsigned long)*8
+   
+   // Compute the necessary size of the vector in bytes.
+   inline unsigned Bytes() {return (Size + BITMAPVECTOR_SIZE - 1)/BITMAPVECTOR_SIZE;};
+   
+   public:
+   
+   inline void Set(unsigned long Elm) 
+      {Vect[Elm/BITMAPVECTOR_SIZE] |= 1 << (Elm%BITMAPVECTOR_SIZE);};
+   inline bool Get(unsigned long Elm) 
+      {return (Vect[Elm/BITMAPVECTOR_SIZE] & (1 << (Elm%BITMAPVECTOR_SIZE))) != 0;};
+   inline void Set(unsigned long Elm,bool To)
+   {
+      if (To)
+        Vect[Elm/BITMAPVECTOR_SIZE] |= 1 << (Elm%BITMAPVECTOR_SIZE);
+      else
+        Vect[Elm/BITMAPVECTOR_SIZE] &= ~(1 << (Elm%BITMAPVECTOR_SIZE));
+   };
+   
+   BitmapVector(unsigned long Size);
+   ~BitmapVector();
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/cmndline.cc b/tools/dsync-0.0/libdsync/contrib/cmndline.cc
new file mode 100644 (file)
index 0000000..8dcdfa9
--- /dev/null
@@ -0,0 +1,347 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: cmndline.cc,v 1.6 1999/11/17 05:59:29 jgg Exp $
+/* ######################################################################
+
+   Command Line Class - Sophisticated command line parser
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/cmndline.h"
+#endif
+#include <dsync/cmndline.h>
+#include <dsync/error.h>
+#include <dsync/strutl.h>
+                                                                       /*}}}*/
+
+// CommandLine::CommandLine - Constructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+CommandLine::CommandLine(Args *AList,Configuration *Conf) : ArgList(AList), 
+                                 Conf(Conf), FileList(0)
+{
+}
+                                                                       /*}}}*/
+// CommandLine::~CommandLine - Destructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+CommandLine::~CommandLine()
+{
+   delete [] FileList;
+}
+                                                                       /*}}}*/
+// CommandLine::Parse - Main action member                             /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool CommandLine::Parse(int argc,const char **argv)
+{
+   delete [] FileList;
+   FileList = new const char *[argc];
+   const char **Files = FileList;
+   int I;
+   for (I = 1; I != argc; I++)
+   {
+      const char *Opt = argv[I];
+      
+      // It is not an option
+      if (*Opt != '-')
+      {
+        *Files++ = Opt;
+        continue;
+      }
+      
+      Opt++;
+      
+      // Double dash signifies the end of option processing
+      if (*Opt == '-' && Opt[1] == 0)
+        break;
+      
+      // Single dash is a short option
+      if (*Opt != '-')
+      {
+        // Iterate over each letter
+        while (*Opt != 0)
+        {          
+           // Search for the option
+           Args *A;
+           for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
+           if (A->end() == true)
+              return _error->Error("Command line option '%c' [from %s] is not known.",*Opt,argv[I]);
+
+           if (HandleOpt(I,argc,argv,Opt,A) == false)
+              return false;
+           if (*Opt != 0)
+              Opt++;          
+        }
+        continue;
+      }
+      
+      Opt++;
+
+      // Match up to a = against the list
+      const char *OptEnd = Opt;
+      Args *A;
+      for (; *OptEnd != 0 && *OptEnd != '='; OptEnd++);
+      for (A = ArgList; A->end() == false && 
+          stringcasecmp(Opt,OptEnd,A->LongOpt) != 0; A++);
+      
+      // Failed, look for a word after the first - (no-foo)
+      bool PreceedMatch = false;
+      if (A->end() == true)
+      {
+        for (; Opt != OptEnd && *Opt != '-'; Opt++);
+
+        if (Opt == OptEnd)
+           return _error->Error("Command line option %s is not understood",argv[I]);
+        Opt++;
+        
+        for (A = ArgList; A->end() == false &&
+             stringcasecmp(Opt,OptEnd,A->LongOpt) != 0; A++);
+
+        // Failed again..
+        if (A->end() == true && OptEnd - Opt != 1)
+           return _error->Error("Command line option %s is not understood",argv[I]);
+
+        // The option could be a single letter option prefixed by a no-..
+        if (A->end() == true)
+        {
+           for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
+           
+           if (A->end() == true)
+              return _error->Error("Command line option %s is not understood",argv[I]);
+        }
+        
+        // The option is not boolean
+        if (A->IsBoolean() == false)
+           return _error->Error("Command line option %s is not boolean",argv[I]);
+        PreceedMatch = true;
+      }
+      
+      // Deal with it.
+      OptEnd--;
+      if (HandleOpt(I,argc,argv,OptEnd,A,PreceedMatch) == false)
+        return false;
+   }
+   
+   // Copy any remaining file names over
+   for (; I != argc; I++)
+      *Files++ = argv[I];
+   *Files = 0;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// CommandLine::HandleOpt - Handle a single option including all flags /*{{{*/
+// ---------------------------------------------------------------------
+/* This is a helper function for parser, it looks at a given argument
+   and looks for specific patterns in the string, it gets tokanized
+   -ruffly- like -*[yes|true|enable]-(o|longopt)[=][ ][argument] */
+bool CommandLine::HandleOpt(int &I,int argc,const char *argv[],
+                           const char *&Opt,Args *A,bool PreceedMatch)
+{
+   const char *Argument = 0;
+   bool CertainArg = false;
+   int IncI = 0;
+
+   /* Determine the possible location of an option or 0 if their is
+      no option */
+   if (Opt[1] == 0 || (Opt[1] == '=' && Opt[2] == 0))
+   {
+      if (I + 1 < argc && argv[I+1][0] != '-')
+        Argument = argv[I+1];
+      
+      // Equals was specified but we fell off the end!
+      if (Opt[1] == '=' && Argument == 0)
+        return _error->Error("Option %s requires an argument.",argv[I]);
+      if (Opt[1] == '=')
+        CertainArg = true;
+        
+      IncI = 1;
+   }
+   else
+   {
+      if (Opt[1] == '=')
+      {
+        CertainArg = true;
+        Argument = Opt + 2;
+      }      
+      else
+        Argument = Opt + 1;
+   }
+   
+   // Option is an argument set
+   if ((A->Flags & HasArg) == HasArg)
+   {
+      if (Argument == 0)
+        return _error->Error("Option %s requires an argument.",argv[I]);
+      Opt += strlen(Opt);
+      I += IncI;
+      
+      // Parse a configuration file
+      if ((A->Flags & ConfigFile) == ConfigFile)
+        return ReadConfigFile(*Conf,Argument);
+
+      // Arbitary item specification
+      if ((A->Flags & ArbItem) == ArbItem)
+      {
+        const char *J;
+        for (J = Argument; *J != 0 && *J != '='; J++);
+        if (*J == 0)
+           return _error->Error("Option %s: Configuration item sepecification must have an =<val>.",argv[I]);
+
+        // = is trailing
+        if (J[1] == 0)
+        {
+           if (I+1 >= argc)
+              return _error->Error("Option %s: Configuration item sepecification must have an =<val>.",argv[I]);
+           Conf->Set(string(Argument,J-Argument),string(argv[I++ +1]));
+        }
+        else
+           Conf->Set(string(Argument,J-Argument),string(J+1));
+        
+        return true;
+      }
+      
+      const char *I = A->ConfName;
+      for (; *I != 0 && *I != ' '; I++);
+      if (*I == ' ')
+        Conf->Set(string(A->ConfName,0,I-A->ConfName),string(I+1) + Argument);
+      else
+        Conf->Set(A->ConfName,string(I) + Argument);
+        
+      return true;
+   }
+   
+   // Option is an integer level
+   if ((A->Flags & IntLevel) == IntLevel)
+   {
+      // There might be an argument
+      if (Argument != 0)
+      {
+        char *EndPtr;
+        unsigned long Value = strtol(Argument,&EndPtr,10);
+        
+        // Conversion failed and the argument was specified with an =s
+        if (EndPtr == Argument && CertainArg == true)
+           return _error->Error("Option %s requires an integer argument, not '%s'",argv[I],Argument);
+
+        // Conversion was ok, set the value and return
+        if (EndPtr != 0 && EndPtr != Argument && *EndPtr == 0)
+        {
+           Conf->Set(A->ConfName,Value);
+           Opt += strlen(Opt);
+           I += IncI;
+           return true;
+        }       
+      }      
+      
+      // Increase the level
+      Conf->Set(A->ConfName,Conf->FindI(A->ConfName)+1);
+      return true;
+   }
+  
+   // Option is a boolean
+   int Sense = -1;  // -1 is unspecified, 0 is yes 1 is no
+
+   // Look for an argument.
+   while (1)
+   {
+      // Look at preceeding text
+      char Buffer[300];
+      if (Argument == 0)
+      {
+        if (PreceedMatch == false)
+           break;
+        
+        if (strlen(argv[I]) >= sizeof(Buffer))
+           return _error->Error("Option '%s' is too long",argv[I]);
+
+        // Skip the leading dash
+        const char *J = argv[I];
+        for (; *J != 0 && *J == '-'; J++);
+        
+        const char *JEnd = J;
+        for (; *JEnd != 0 && *JEnd != '-'; JEnd++);
+        if (*JEnd != 0)
+        {
+           strncpy(Buffer,J,JEnd - J);
+           Buffer[JEnd - J] = 0;
+           Argument = Buffer;
+           CertainArg = true;
+        }       
+        else
+           break;
+      }
+
+      // Check for boolean
+      Sense = StringToBool(Argument);
+      if (Sense >= 0)
+      {
+        // Eat the argument     
+        if (Argument != Buffer)
+        {
+           Opt += strlen(Opt);
+           I += IncI;
+        }       
+        break;
+      }
+
+      if (CertainArg == true)
+        return _error->Error("Sense %s is not understood, try true or false.",Argument);
+      
+      Argument = 0;
+   }
+      
+   // Indeterminate sense depends on the flag
+   if (Sense == -1)
+   {
+      if ((A->Flags & InvBoolean) == InvBoolean)
+        Sense = 0;
+      else
+        Sense = 1;
+   }
+   
+   Conf->Set(A->ConfName,Sense);
+   return true;
+}
+                                                                       /*}}}*/
+// CommandLine::FileSize - Count the number of filenames               /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+unsigned int CommandLine::FileSize() const
+{
+   unsigned int Count = 0;
+   for (const char **I = FileList; I != 0 && *I != 0; I++)
+      Count++;
+   return Count;
+}
+                                                                       /*}}}*/
+// CommandLine::DispatchArg - Do something with the first arg          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool CommandLine::DispatchArg(Dispatch *Map,bool NoMatch)
+{
+   int I;
+   for (I = 0; Map[I].Match != 0; I++)
+   {
+      if (strcmp(FileList[0],Map[I].Match) == 0)
+      {
+        bool Res = Map[I].Handler(*this);
+        if (Res == false && _error->PendingError() == false)
+           _error->Error("Handler silently failed");
+        return Res;
+      }
+   }
+   
+   // No matching name
+   if (Map[I].Match == 0)
+   {
+      if (NoMatch == true)
+        _error->Error("Invalid operation %s",FileList[0]);
+   }
+   
+   return false;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/cmndline.h b/tools/dsync-0.0/libdsync/contrib/cmndline.h
new file mode 100644 (file)
index 0000000..ca8ba94
--- /dev/null
@@ -0,0 +1,103 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: cmndline.h,v 1.2 1998/12/29 04:38:09 jgg Exp $
+/* ######################################################################
+
+   Command Line Class - Sophisticated command line parser
+   
+   This class provides a unified command line parser/option handliner/
+   configuration mechanism. It allows the caller to specify the option
+   set and map the option set into the configuration class or other
+   special functioning.
+   
+   Filenames are stripped from the option stream and put into their
+   own array.
+   
+   The argument descriptor array can be initialized as:
+   
+ CommandLine::Args Args[] = 
+ {{'q',"quiet","apt::get::quiet",CommandLine::IntLevel},
+  {0,0,0,0,0}};
+   
+   The flags mean,
+     HasArg - Means the argument has a value
+     IntLevel - Means the argument is an integer level indication, the
+                following -qqqq (+3) -q5 (=5) -q=5 (=5) are valid
+     Boolean  - Means it is true/false or yes/no. 
+                -d (true) --no-d (false) --yes-d (true)
+                --long (true) --no-long (false) --yes-long (true)
+                -d=yes (true) -d=no (false) Words like enable, disable,
+                true false, yes no and on off are recognized in logical 
+                places.
+     InvBoolean - Same as boolean but the case with no specified sense
+                  (first case) is set to false.
+     ConfigFile - Means this flag should be interprited as the name of 
+                  a config file to read in at this point in option processing.
+                  Implies HasArg.
+   The default, if the flags are 0 is to use Boolean
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef PKGLIB_CMNDLINE_H
+#define PKGLIB_CMNDLINE_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/cmndline.h"
+#endif 
+
+#include <dsync/configuration.h>
+
+class CommandLine
+{
+   public:
+   struct Args;
+   struct Dispatch;
+   
+   protected:
+   
+   Args *ArgList;
+   Configuration *Conf;
+   bool HandleOpt(int &I,int argc,const char *argv[],
+                 const char *&Opt,Args *A,bool PreceedeMatch = false);
+
+   public:
+   
+   enum AFlags 
+   {
+      HasArg = (1 << 0),
+      IntLevel = (1 << 1),
+      Boolean = (1 << 2),
+      InvBoolean = (1 << 3),
+      ConfigFile = (1 << 4) | HasArg,
+      ArbItem = (1 << 5) | HasArg
+   };
+
+   const char **FileList;
+   
+   bool Parse(int argc,const char **argv);
+   void ShowHelp();
+   unsigned int FileSize() const;
+   bool DispatchArg(Dispatch *List,bool NoMatch = true);
+      
+   CommandLine(Args *AList,Configuration *Conf);
+   ~CommandLine();
+};
+
+struct CommandLine::Args
+{
+   char ShortOpt;
+   const char *LongOpt;
+   const char *ConfName;
+   unsigned long Flags;
+   
+   inline bool end() {return ShortOpt == 0 && LongOpt == 0;};
+   inline bool IsBoolean() {return Flags == 0 || (Flags & (Boolean|InvBoolean)) != 0;};
+};
+
+struct CommandLine::Dispatch
+{
+   const char *Match;
+   bool (*Handler)(CommandLine &);
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/configuration.cc b/tools/dsync-0.0/libdsync/contrib/configuration.cc
new file mode 100644 (file)
index 0000000..4ce5247
--- /dev/null
@@ -0,0 +1,456 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: configuration.cc,v 1.5 1999/11/17 05:59:29 jgg Exp $
+/* ######################################################################
+
+   Configuration Class
+   
+   This class provides a configuration file and command line parser
+   for a tree-oriented configuration environment. All runtime configuration
+   is stored in here.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/configuration.h"
+#endif
+#include <dsync/configuration.h>
+#include <dsync/error.h>
+#include <dsync/strutl.h>
+
+#include <stdio.h>
+#include <fstream>
+#include <iostream>
+using namespace std;
+                                                                       /*}}}*/
+
+Configuration *_config = new Configuration;
+
+// Configuration::Configuration - Constructor                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+Configuration::Configuration()
+{
+   Root = new Item;
+}
+                                                                       /*}}}*/
+// Configuration::Lookup - Lookup a single item                                /*{{{*/
+// ---------------------------------------------------------------------
+/* This will lookup a single item by name below another item. It is a 
+   helper function for the main lookup function */
+Configuration::Item *Configuration::Lookup(Item *Head,const char *S,
+                                          unsigned long Len,bool Create)
+{
+   int Res = 1;
+   Item *I = Head->Child;
+   Item **Last = &Head->Child;
+   
+   // Empty strings match nothing. They are used for lists.
+   if (Len != 0)
+   {
+      for (; I != 0; Last = &I->Next, I = I->Next)
+        if ((Res = stringcasecmp(I->Tag.c_str(), I->Tag.c_str() + strlen(I->Tag.c_str()),S,S + Len)) == 0)
+           break;
+   }
+   else
+      for (; I != 0; Last = &I->Next, I = I->Next);
+      
+   if (Res == 0)
+      return I;
+   if (Create == false)
+      return 0;
+   
+   I = new Item;
+   I->Tag = string(S,Len);
+   I->Next = *Last;
+   I->Parent = Head;
+   *Last = I;
+   return I;
+}
+                                                                       /*}}}*/
+// Configuration::Lookup - Lookup a fully scoped item                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This performs a fully scoped lookup of a given name, possibly creating
+   new items */
+Configuration::Item *Configuration::Lookup(const char *Name,bool Create)
+{
+   if (Name == 0)
+      return Root->Child;
+   
+   const char *Start = Name;
+   const char *End = Start + strlen(Name);
+   const char *TagEnd = Name;
+   Item *Itm = Root;
+   for (; End - TagEnd >= 2; TagEnd++)
+   {
+      if (TagEnd[0] == ':' && TagEnd[1] == ':')
+      {
+        Itm = Lookup(Itm,Start,TagEnd - Start,Create);
+        if (Itm == 0)
+           return 0;
+        TagEnd = Start = TagEnd + 2;    
+      }
+   }   
+
+   // This must be a trailing ::, we create unique items in a list
+   if (End - Start == 0)
+   {
+      if (Create == false)
+        return 0;
+   }
+   
+   Itm = Lookup(Itm,Start,End - Start,Create);
+   return Itm;
+}
+                                                                       /*}}}*/
+// Configuration::Find - Find a value                                  /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+string Configuration::Find(const char *Name,const char *Default)
+{
+   Item *Itm = Lookup(Name,false);
+   if (Itm == 0 || Itm->Value.empty() == true)
+   {
+      if (Default == 0)
+        return string();
+      else
+        return Default;
+   }
+   
+   return Itm->Value;
+}
+                                                                       /*}}}*/
+// Configuration::FindFile - Find a Filename                           /*{{{*/
+// ---------------------------------------------------------------------
+/* Directories are stored as the base dir in the Parent node and the
+   sub directory in sub nodes with the final node being the end filename
+ */
+string Configuration::FindFile(const char *Name,const char *Default)
+{
+   Item *Itm = Lookup(Name,false);
+   if (Itm == 0 || Itm->Value.empty() == true)
+   {
+      if (Default == 0)
+        return string();
+      else
+        return Default;
+   }
+   
+   // Absolute path
+   if (Itm->Value[0] == '/' || Itm->Parent == 0)
+      return Itm->Value;
+   
+   // ./ is also considered absolute as is anything with ~ in it
+   if (Itm->Value[0] != 0 && 
+       ((Itm->Value[0] == '.' && Itm->Value[1] == '/') ||
+       (Itm->Value[0] == '~' && Itm->Value[1] == '/')))
+      return Itm->Value;
+   
+   if (Itm->Parent->Value.end()[-1] == '/')
+      return Itm->Parent->Value + Itm->Value;
+   else
+      return Itm->Parent->Value + '/' + Itm->Value;
+}
+                                                                       /*}}}*/
+// Configuration::FindDir - Find a directory name                      /*{{{*/
+// ---------------------------------------------------------------------
+/* This is like findfile execept the result is terminated in a / */
+string Configuration::FindDir(const char *Name,const char *Default)
+{
+   string Res = FindFile(Name,Default);
+   if (Res.end()[-1] != '/')
+      return Res + '/';
+   return Res;
+}
+                                                                       /*}}}*/
+// Configuration::FindI - Find an integer value                                /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+int Configuration::FindI(const char *Name,int Default)
+{
+   Item *Itm = Lookup(Name,false);
+   if (Itm == 0 || Itm->Value.empty() == true)
+      return Default;
+   
+   char *End;
+   int Res = strtol(Itm->Value.c_str(),&End,0);
+   if (End == Itm->Value.c_str())
+      return Default;
+   
+   return Res;
+}
+                                                                       /*}}}*/
+// Configuration::FindB - Find a boolean type                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool Configuration::FindB(const char *Name,bool Default)
+{
+   Item *Itm = Lookup(Name,false);
+   if (Itm == 0 || Itm->Value.empty() == true)
+      return Default;
+   
+   return StringToBool(Itm->Value,Default);
+}
+                                                                       /*}}}*/
+// Configuration::Set - Set a value                                    /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void Configuration::Set(const char *Name,string Value)
+{
+   Item *Itm = Lookup(Name,true);
+   if (Itm == 0)
+      return;
+   Itm->Value = Value;
+}
+                                                                       /*}}}*/
+// Configuration::Set - Set an integer value                           /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void Configuration::Set(const char *Name,int Value)
+{
+   Item *Itm = Lookup(Name,true);
+   if (Itm == 0)
+      return;
+   char S[300];
+   snprintf(S,sizeof(S),"%i",Value);
+   Itm->Value = S;
+}
+                                                                       /*}}}*/
+// Configuration::Exists - Returns true if the Name exists             /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool Configuration::Exists(const char *Name)
+{
+   Item *Itm = Lookup(Name,false);
+   if (Itm == 0)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// Configuration::Dump - Dump the config                               /*{{{*/
+// ---------------------------------------------------------------------
+/* Dump the entire configuration space */
+void Configuration::Dump()
+{
+   /* Write out all of the configuration directives by walking the 
+      configuration tree */
+   const Configuration::Item *Top = _config->Tree(0);
+   for (; Top != 0;)
+   {
+      clog << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
+      
+      if (Top->Child != 0)
+      {
+        Top = Top->Child;
+        continue;
+      }
+      
+      while (Top != 0 && Top->Next == 0)
+        Top = Top->Parent;
+      if (Top != 0)
+        Top = Top->Next;
+   }   
+}
+                                                                       /*}}}*/
+
+// Configuration::Item::FullTag - Return the fully scoped tag          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+string Configuration::Item::FullTag() const
+{
+   if (Parent == 0 || Parent->Parent == 0)
+      return Tag;
+   return Parent->FullTag() + "::" + Tag;
+}
+                                                                       /*}}}*/
+
+// ReadConfigFile - Read a configuration file                          /*{{{*/
+// ---------------------------------------------------------------------
+/* The configuration format is very much like the named.conf format
+   used in bind8, in fact this routine can parse most named.conf files. */
+bool ReadConfigFile(Configuration &Conf,string FName)
+{
+   // Open the stream for reading
+   ifstream F(FName.c_str(),ios::in);
+   if (!F != 0)
+      return _error->Errno("ifstream::ifstream","Opening configuration file %s",FName.c_str());
+   
+   char Buffer[300];
+   string LineBuffer;
+   string Stack[100];
+   unsigned int StackPos = 0;
+   
+   // Parser state
+   string ParentTag;
+   
+   int CurLine = 0;
+   bool InComment = false;
+   while (F.eof() == false)
+   {
+      F.getline(Buffer,sizeof(Buffer));
+      CurLine++;
+      _strtabexpand(Buffer,sizeof(Buffer));
+      _strstrip(Buffer);
+
+      // Multi line comment
+      if (InComment == true)
+      {
+        for (const char *I = Buffer; *I != 0; I++)
+        {
+           if (*I == '*' && I[1] == '/')
+           {
+              memmove(Buffer,I+2,strlen(I+2) + 1);
+              InComment = false;
+              break;
+           }       
+        }
+        if (InComment == true)
+           continue;
+      }
+      
+      // Discard single line comments
+      bool InQuote = false;
+      for (char *I = Buffer; *I != 0; I++)
+      {
+        if (*I == '"')
+           InQuote = !InQuote;
+        if (InQuote == true)
+           continue;
+        
+        if (*I == '/' && I[1] == '/')
+         {
+           *I = 0;
+           break;
+        }
+      }
+      
+      // Look for multi line comments
+      for (char *I = Buffer; *I != 0; I++)
+      {
+        if (*I == '"')
+           InQuote = !InQuote;
+        if (InQuote == true)
+           continue;
+        
+        if (*I == '/' && I[1] == '*')
+         {
+           InComment = true;
+           for (char *J = Buffer; *J != 0; J++)
+           {
+              if (*J == '*' && J[1] == '/')
+              {
+                 memmove(I,J+2,strlen(J+2) + 1);
+                 InComment = false;
+                 break;
+              }               
+           }
+           
+           if (InComment == true)
+           {
+              *I = 0;
+              break;
+           }       
+        }
+      }
+      
+      // Blank
+      if (Buffer[0] == 0)
+        continue;
+      
+      // We now have a valid line fragment
+      for (char *I = Buffer; *I != 0;)
+      {
+        if (*I == '{' || *I == ';' || *I == '}')
+        {
+           // Put the last fragement into the buffer
+           char *Start = Buffer;
+           char *Stop = I;
+           for (; Start != I && isspace(*Start) != 0; Start++);
+           for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
+           if (LineBuffer.empty() == false && Stop - Start != 0)
+              LineBuffer += ' ';
+           LineBuffer += string(Start,Stop - Start);
+           
+           // Remove the fragment
+           char TermChar = *I;
+           memmove(Buffer,I + 1,strlen(I + 1) + 1);
+           I = Buffer;
+
+           // Move up a tag
+           if (TermChar == '}')
+           {
+              if (StackPos == 0)
+                 ParentTag = string();
+              else
+                 ParentTag = Stack[--StackPos];
+           }
+           
+           // Syntax Error
+           if (TermChar == '{' && LineBuffer.empty() == true)
+              return _error->Error("Syntax error %s:%u: Block starts with no name.",FName.c_str(),CurLine);
+           
+           if (LineBuffer.empty() == true)
+              continue;
+
+           // Parse off the tag
+           string Tag;
+           const char *Pos = LineBuffer.c_str();
+           if (ParseQuoteWord(Pos,Tag) == false)
+              return _error->Error("Syntax error %s:%u: Malformed Tag",FName.c_str(),CurLine);     
+           
+           // Go down a level
+           if (TermChar == '{')
+           {
+              if (StackPos <= 100)
+                 Stack[StackPos++] = ParentTag;
+              if (ParentTag.empty() == true)
+                 ParentTag = Tag;
+              else
+                 ParentTag += string("::") + Tag;
+              Tag = string();
+           }
+
+           // Parse off the word
+           string Word;
+           if (ParseCWord(Pos,Word) == false)
+           {
+              if (TermChar != '{')
+              {
+                 Word = Tag;
+                 Tag = "";
+              }               
+           }
+           
+           // Generate the item name
+           string Item;
+           if (ParentTag.empty() == true)
+              Item = Tag;
+           else
+           {
+              if (TermChar != '{' || Tag.empty() == false)
+                 Item = ParentTag + "::" + Tag;
+              else
+                 Item = ParentTag;
+           }
+           
+           // Set the item in the configuration class
+           Conf.Set(Item,Word);
+                           
+           // Empty the buffer
+           LineBuffer = string();
+        }
+        else
+           I++;
+      }
+
+      // Store the fragment
+      const char *Stripd = _strstrip(Buffer);
+      if (*Stripd != 0 && LineBuffer.empty() == false)
+        LineBuffer += " ";
+      LineBuffer += Stripd;
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/configuration.h b/tools/dsync-0.0/libdsync/contrib/configuration.h
new file mode 100644 (file)
index 0000000..10bd909
--- /dev/null
@@ -0,0 +1,88 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: configuration.h,v 1.4 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+
+   Configuration Class
+   
+   This class provides a configuration file and command line parser
+   for a tree-oriented configuration environment. All runtime configuration
+   is stored in here.
+   
+   Each configuration name is given as a fully scoped string such as
+     Foo::Bar
+   And has associated with it a text string. The Configuration class only
+   provides storage and lookup for this tree, other classes provide
+   configuration file formats (and parsers/emitters if needed).
+
+   Most things can get by quite happily with,
+     cout << _config->Find("Foo::Bar") << endl;
+
+   A special extension, support for ordered lists is provided by using the
+   special syntax, "block::list::" the trailing :: designates the 
+   item as a list. To access the list you must use the tree function on
+   "block::list".
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef PKGLIB_CONFIGURATION_H
+#define PKGLIB_CONFIGURATION_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/configuration.h"
+#endif 
+
+#include <string>
+using namespace std;
+
+class Configuration
+{
+public:
+   struct Item
+   {
+      string Value;
+      string Tag;
+      Item *Parent;
+      Item *Child;
+      Item *Next;
+      
+      string FullTag() const;
+      
+      Item() : Parent(0), Child(0), Next(0) {};
+   };
+private:
+   Item *Root;
+   
+   Item *Lookup(Item *Head,const char *S,unsigned long Len,bool Create);
+   Item *Lookup(const char *Name,bool Create);
+      
+   public:
+
+   string Find(const char *Name,const char *Default = 0);
+   string Find(string Name,const char *Default = 0) {return Find(Name.c_str(),Default);};
+   string FindFile(const char *Name,const char *Default = 0);
+   string FindDir(const char *Name,const char *Default = 0);
+   int FindI(const char *Name,int Default = 0);
+//   int FindI(string Name,bool Default = 0) {return FindI(Name.c_str(),Default);};
+   bool FindB(const char *Name,bool Default = false);
+//   bool FindB(string Name,bool Default = false) {return FindB(Name.c_str(),Default);};
+
+   inline void Set(string Name,string Value) {Set(Name.c_str(),Value);};
+   void Set(const char *Name,string Value);
+   void Set(const char *Name,int Value);   
+   
+   inline bool Exists(string Name) {return Exists(Name.c_str());};
+   bool Exists(const char *Name);
+      
+   inline const Item *Tree(const char *Name) {return Lookup(Name,false);};
+
+   void Dump();
+   
+   Configuration();
+};
+
+extern Configuration *_config;
+
+bool ReadConfigFile(Configuration &Conf,string File);
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/error.cc b/tools/dsync-0.0/libdsync/contrib/error.cc
new file mode 100644 (file)
index 0000000..4d48b82
--- /dev/null
@@ -0,0 +1,242 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: error.cc,v 1.4 1999/01/19 04:41:43 jgg Exp $
+/* ######################################################################
+   
+   Global Erorr Class - Global error mechanism
+
+   We use a simple STL vector to store each error record. A PendingFlag
+   is kept which indicates when the vector contains a Sever error.
+   
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Jason Gunthorpe.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include Files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/error.h"
+#endif 
+
+#include <dsync/error.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <iostream>
+
+using namespace std;
+                                                                       /*}}}*/
+
+// Global Error Object                                                 /*{{{*/
+/* If the implementation supports posix threads then the accessor function
+   is compiled to be thread safe otherwise a non-safe version is used. A
+   Per-Thread error object is maintained in much the same manner as libc
+   manages errno */
+#if _POSIX_THREADS == 1
+ #include <pthread.h>
+
+ static pthread_key_t ErrorKey;
+ static bool GotKey = false;
+ static void ErrorDestroy(void *Obj) {delete (GlobalError *)Obj;};
+ static void KeyAlloc() {GotKey = true; 
+    pthread_key_create(&ErrorKey,ErrorDestroy);};
+
+ GlobalError *_GetErrorObj()
+ {
+    static pthread_once_t Once = PTHREAD_ONCE_INIT;
+    pthread_once(&Once,KeyAlloc);
+
+    /* Solaris has broken pthread_once support, isn't that nice? Thus
+       we create a race condition for such defective systems here. */
+    if (GotKey == false)
+       KeyAlloc();
+   
+    void *Res = pthread_getspecific(ErrorKey);
+    if (Res == 0)
+       pthread_setspecific(ErrorKey,Res = new GlobalError);
+    return (GlobalError *)Res;
+ }
+#else
+ GlobalError *_GetErrorObj()
+ {
+    static GlobalError *Obj = new GlobalError;
+    return Obj;
+ }
+#endif
+                                                                       /*}}}*/
+
+// GlobalError::GlobalError - Constructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+GlobalError::GlobalError() : List(0), PendingFlag(false)
+{
+}
+                                                                       /*}}}*/
+// GlobalError::Errno - Get part of the error string from errno                /*{{{*/
+// ---------------------------------------------------------------------
+/* Function indicates the stdlib function that failed and Description is
+   a user string that leads the text. Form is:
+     Description - Function (errno: strerror)
+   Carefull of the buffer overrun, sprintf.
+ */
+bool GlobalError::Errno(const char *Function,const char *Description,...)
+{
+   va_list args;
+   va_start(args,Description);
+
+   // sprintf the description
+   char S[400];
+   vsnprintf(S,sizeof(S),Description,args);
+   snprintf(S + strlen(S),sizeof(S) - strlen(S),
+           " - %s (%i %s)",Function,errno,strerror(errno));
+
+   // Put it on the list
+   Item *Itm = new Item;
+   Itm->Text = S;
+   Itm->Error = true;
+   Insert(Itm);
+   
+   PendingFlag = true;
+
+   return false;   
+}
+                                                                       /*}}}*/
+// GlobalError::WarningE - Get part of the warn string from errno      /*{{{*/
+// ---------------------------------------------------------------------
+/* Function indicates the stdlib function that failed and Description is
+   a user string that leads the text. Form is:
+     Description - Function (errno: strerror)
+   Carefull of the buffer overrun, sprintf.
+ */
+bool GlobalError::WarningE(const char *Function,const char *Description,...)
+{
+   va_list args;
+   va_start(args,Description);
+
+   // sprintf the description
+   char S[400];
+   vsnprintf(S,sizeof(S),Description,args);
+   snprintf(S + strlen(S),sizeof(S) - strlen(S)," - %s (%i %s)",Function,errno,strerror(errno));
+
+   // Put it on the list
+   Item *Itm = new Item;
+   Itm->Text = S;
+   Itm->Error = false;
+   Insert(Itm);
+   
+   return false;   
+}
+                                                                       /*}}}*/
+// GlobalError::Error - Add an error to the list                       /*{{{*/
+// ---------------------------------------------------------------------
+/* Just vsprintfs and pushes */
+bool GlobalError::Error(const char *Description,...)
+{
+   va_list args;
+   va_start(args,Description);
+
+   // sprintf the description
+   char S[400];
+   vsnprintf(S,sizeof(S),Description,args);
+
+   // Put it on the list
+   Item *Itm = new Item;
+   Itm->Text = S;
+   Itm->Error = true;
+   Insert(Itm);
+   
+   PendingFlag = true;
+   
+   return false;
+}
+                                                                       /*}}}*/
+// GlobalError::Warning - Add a warning to the list                    /*{{{*/
+// ---------------------------------------------------------------------
+/* This doesn't set the pending error flag */
+bool GlobalError::Warning(const char *Description,...)
+{
+   va_list args;
+   va_start(args,Description);
+
+   // sprintf the description
+   char S[400];
+   vsnprintf(S,sizeof(S),Description,args);
+
+   // Put it on the list
+   Item *Itm = new Item;
+   Itm->Text = S;
+   Itm->Error = false;
+   Insert(Itm);
+   
+   return false;
+}
+                                                                       /*}}}*/
+// GlobalError::PopMessage - Pulls a single message out                        /*{{{*/
+// ---------------------------------------------------------------------
+/* This should be used in a loop checking empty() each cycle. It returns
+   true if the message is an error. */
+bool GlobalError::PopMessage(string &Text)
+{
+   if (List == 0)
+      return false;
+      
+   bool Ret = List->Error;
+   Text = List->Text;
+   Item *Old = List;
+   List = List->Next;
+   delete Old;
+   
+   // This really should check the list to see if only warnings are left..
+   if (List == 0)
+      PendingFlag = false;
+   
+   return Ret;
+}
+                                                                       /*}}}*/
+// GlobalError::DumpErrors - Dump all of the errors/warns to cerr      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void GlobalError::DumpErrors()
+{
+   // Print any errors or warnings found
+   string Err;
+   while (empty() == false)
+   {
+      bool Type = PopMessage(Err);
+      if (Type == true)
+        std::cerr << "E: " << Err << endl;
+      else
+        cerr << "W: " << Err << endl;
+   }
+}
+                                                                       /*}}}*/
+// GlobalError::Discard - Discard                                                                      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void GlobalError::Discard()
+{
+   while (List != 0)
+   {
+      Item *Old = List;
+      List = List->Next;
+      delete Old;
+   }
+   
+   PendingFlag = false;
+};
+                                                                       /*}}}*/
+// GlobalError::Insert - Insert a new item at the end                  /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void GlobalError::Insert(Item *Itm)
+{
+   Item **End = &List;
+   for (Item *I = List; I != 0; I = I->Next)
+      End = &I->Next;
+   Itm->Next = *End;
+   *End = Itm;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/error.h b/tools/dsync-0.0/libdsync/contrib/error.h
new file mode 100644 (file)
index 0000000..4ff988a
--- /dev/null
@@ -0,0 +1,90 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: error.h,v 1.2 1998/12/29 04:38:09 jgg Exp $
+/* ######################################################################
+   
+   Global Erorr Class - Global error mechanism
+
+   This class has a single global instance. When a function needs to 
+   generate an error condition, such as a read error, it calls a member
+   in this class to add the error to a stack of errors. 
+   
+   By using a stack the problem with a scheme like errno is removed and
+   it allows a very detailed account of what went wrong to be transmitted
+   to the UI for display. (Errno has problems because each function sets
+   errno to 0 if it didn't have an error thus eraseing erno in the process
+   of cleanup)
+   
+   Several predefined error generators are provided to handle common 
+   things like errno. The general idea is that all methods return a bool.
+   If the bool is true then things are OK, if it is false then things 
+   should start being undone and the stack should unwind under program
+   control.
+   
+   A Warning should not force the return of false. Things did not fail, but
+   they might have had unexpected problems. Errors are stored in a FIFO
+   so Pop will return the first item..
+   
+   I have some thoughts about extending this into a more general UI<-> 
+   Engine interface, ie allowing the Engine to say 'The disk is full' in 
+   a dialog that says 'Panic' and 'Retry'.. The error generator functions
+   like errno, Warning and Error return false always so this is normal:
+     if (open(..))
+        return _error->Errno(..);
+   
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Jason Gunthorpe.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef PKGLIB_ERROR_H
+#define PKGLIB_ERROR_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/error.h"
+#endif 
+
+#include <string>
+using namespace std;
+
+class GlobalError
+{
+   struct Item
+   {
+      string Text;
+      bool Error;
+      Item *Next;
+   };
+   
+   Item *List;
+   bool PendingFlag;
+   void Insert(Item *I);
+   
+   public:
+
+   // Call to generate an error from a library call.
+   bool Errno(const char *Function,const char *Description,...);
+   bool WarningE(const char *Function,const char *Description,...);
+
+   /* A warning should be considered less severe than an error, and may be
+      ignored by the client. */
+   bool Error(const char *Description,...);
+   bool Warning(const char *Description,...);
+
+   // Simple accessors
+   inline bool PendingError() {return PendingFlag;};
+   inline bool empty() {return List == 0;};
+   bool PopMessage(string &Text);
+   void Discard();
+
+   // Usefull routine to dump to cerr
+   void DumpErrors();
+   
+   GlobalError();
+};
+
+// The 'extra-ansi' syntax is used to help with collisions. 
+GlobalError *_GetErrorObj();
+#define _error _GetErrorObj()
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/fileutl.cc b/tools/dsync-0.0/libdsync/contrib/fileutl.cc
new file mode 100644 (file)
index 0000000..44f8b2a
--- /dev/null
@@ -0,0 +1,534 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: fileutl.cc,v 1.5 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+   
+   File Utilities
+   
+   CopyFile - Buffered copy of a single file
+   GetLock - dpkg compatible lock file manipulation (fcntl)
+   
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Jason Gunthorpe.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include Files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/fileutl.h"
+#endif 
+#include <dsync/fileutl.h>
+#include <dsync/error.h>
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <wait.h>
+#include <errno.h>
+#include <iostream>
+
+using namespace std;
+                                                                       /*}}}*/
+
+// CopyFile - Buffered copy of a file                                  /*{{{*/
+// ---------------------------------------------------------------------
+/* The caller is expected to set things so that failure causes erasure */
+bool CopyFile(FileFd &From,FileFd &To)
+{
+   if (From.IsOpen() == false || To.IsOpen() == false)
+      return false;
+   
+   // Buffered copy between fds
+   unsigned char *Buf = new unsigned char[64000];
+   unsigned long Size = From.Size();
+   while (Size != 0)
+   {
+      unsigned long ToRead = Size;
+      if (Size > 64000)
+        ToRead = 64000;
+      
+      if (From.Read(Buf,ToRead) == false || 
+         To.Write(Buf,ToRead) == false)
+      {
+        delete [] Buf;
+        return false;
+      }
+      
+      Size -= ToRead;
+   }
+
+   delete [] Buf;
+   return true;   
+}
+                                                                       /*}}}*/
+// GetLock - Gets a lock file                                          /*{{{*/
+// ---------------------------------------------------------------------
+/* This will create an empty file of the given name and lock it. Once this
+   is done all other calls to GetLock in any other process will fail with
+   -1. The return result is the fd of the file, the call should call
+   close at some time. */
+int GetLock(string File,bool Errors)
+{
+   int FD = open(File.c_str(),O_RDWR | O_CREAT | O_TRUNC,0640);
+   if (FD < 0)
+   {
+      if (Errors == true)
+        _error->Errno("open","Could not open lock file %s",File.c_str());
+      return -1;
+   }
+   
+   // Aquire a write lock
+   struct flock fl;
+   fl.l_type = F_WRLCK;
+   fl.l_whence = SEEK_SET;
+   fl.l_start = 0;
+   fl.l_len = 0;
+   if (fcntl(FD,F_SETLK,&fl) == -1)
+   {
+      if (errno == ENOLCK)
+      {
+        _error->Warning("Not using locking for nfs mounted lock file %s",File.c_str());
+        return true;
+      }      
+      if (Errors == true)
+        _error->Errno("open","Could not get lock %s",File.c_str());
+      close(FD);
+      return -1;
+   }
+
+   return FD;
+}
+                                                                       /*}}}*/
+// FileExists - Check if a file exists                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool FileExists(string File)
+{
+   struct stat Buf;
+   if (stat(File.c_str(),&Buf) != 0)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// SafeGetCWD - This is a safer getcwd that returns a dynamic string   /*{{{*/
+// ---------------------------------------------------------------------
+/* We return / on failure. */
+string SafeGetCWD()
+{
+   // Stash the current dir.
+   char S[300];
+   S[0] = 0;
+   if (getcwd(S,sizeof(S)-2) == 0)
+      return "/";
+   unsigned int Len = strlen(S);
+   S[Len] = '/';
+   S[Len+1] = 0;
+   return S;
+}
+                                                                       /*}}}*/
+// flNotDir - Strip the directory from the filename                    /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+string flNotDir(string File)
+{
+   string::size_type Res = File.rfind('/');
+   if (Res == string::npos)
+      return File;
+   Res++;
+   return string(File,Res,Res - File.length());
+}
+                                                                       /*}}}*/
+// flNotFile - Strip the file from the directory name                  /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+string flNotFile(string File)
+{
+   string::size_type Res = File.rfind('/');
+   if (Res == string::npos)
+      return File;
+   Res++;
+   return string(File,0,Res);
+}
+                                                                       /*}}}*/
+// flNoLink - If file is a symlink then deref it                       /*{{{*/
+// ---------------------------------------------------------------------
+/* If the name is not a link then the returned path is the input. */
+string flNoLink(string File)
+{
+   struct stat St;
+   if (lstat(File.c_str(),&St) != 0 || S_ISLNK(St.st_mode) == 0)
+      return File;
+   if (stat(File.c_str(),&St) != 0)
+      return File;
+   
+   /* Loop resolving the link. There is no need to limit the number of 
+      loops because the stat call above ensures that the symlink is not 
+      circular */
+   char Buffer[1024];
+   string NFile = File;
+   while (1)
+   {
+      // Read the link
+      int Res;
+      if ((Res = readlink(NFile.c_str(),Buffer,sizeof(Buffer))) <= 0 || 
+         (unsigned)Res >= sizeof(Buffer))
+         return File;
+      
+      // Append or replace the previous path
+      Buffer[Res] = 0;
+      if (Buffer[0] == '/')
+        NFile = Buffer;
+      else
+        NFile = flNotFile(NFile) + Buffer;
+      
+      // See if we are done
+      if (lstat(NFile.c_str(),&St) != 0)
+        return File;
+      if (S_ISLNK(St.st_mode) == 0)
+        return NFile;      
+   }   
+}
+                                                                       /*}}}*/
+// SetCloseExec - Set the close on exec flag                           /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void SetCloseExec(int Fd,bool Close)
+{   
+   if (fcntl(Fd,F_SETFD,(Close == false)?0:FD_CLOEXEC) != 0)
+   {
+      cerr << "FATAL -> Could not set close on exec " << strerror(errno) << endl;
+      exit(100);
+   }
+}
+                                                                       /*}}}*/
+// SetNonBlock - Set the nonblocking flag                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void SetNonBlock(int Fd,bool Block)
+{   
+   int Flags = fcntl(Fd,F_GETFL) & (~O_NONBLOCK);
+   if (fcntl(Fd,F_SETFL,Flags | ((Block == false)?0:O_NONBLOCK)) != 0)
+   {
+      cerr << "FATAL -> Could not set non-blocking flag " << strerror(errno) << endl;
+      exit(100);
+   }
+}
+                                                                       /*}}}*/
+// WaitFd - Wait for a FD to become readable                           /*{{{*/
+// ---------------------------------------------------------------------
+/* This waits for a FD to become readable using select. It is usefull for
+   applications making use of non-blocking sockets. The timeout is 
+   in seconds. */
+bool WaitFd(int Fd,bool write,unsigned long timeout)
+{
+   fd_set Set;
+   struct timeval tv;
+   FD_ZERO(&Set);
+   FD_SET(Fd,&Set);
+   tv.tv_sec = timeout;
+   tv.tv_usec = 0;
+   if (write == true) 
+   {      
+      int Res;
+      do
+      {
+        Res = select(Fd+1,0,&Set,0,(timeout != 0?&tv:0));
+      }
+      while (Res < 0 && errno == EINTR);
+      
+      if (Res <= 0)
+        return false;
+   } 
+   else 
+   {
+      int Res;
+      do
+      {
+        Res = select(Fd+1,&Set,0,0,(timeout != 0?&tv:0));
+      }
+      while (Res < 0 && errno == EINTR);
+      
+      if (Res <= 0)
+        return false;
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// ExecFork - Magical fork that sanitizes the context before execing   /*{{{*/
+// ---------------------------------------------------------------------
+/* This is used if you want to cleanse the environment for the forked 
+   child, it fixes up the important signals and nukes all of the fds,
+   otherwise acts like normal fork. */
+int ExecFork()
+{
+   // Fork off the process
+   pid_t Process = fork();
+   if (Process < 0)
+   {
+      cerr << "FATAL -> Failed to fork." << endl;
+      exit(100);
+   }
+
+   // Spawn the subprocess
+   if (Process == 0)
+   {
+      // Setup the signals
+      signal(SIGPIPE,SIG_DFL);
+      signal(SIGQUIT,SIG_DFL);
+      signal(SIGINT,SIG_DFL);
+      signal(SIGWINCH,SIG_DFL);
+      signal(SIGCONT,SIG_DFL);
+      signal(SIGTSTP,SIG_DFL);
+      
+      // Close all of our FDs - just in case
+      for (int K = 3; K != 40; K++)
+        fcntl(K,F_SETFD,FD_CLOEXEC);
+   }
+   
+   return Process;
+}
+                                                                       /*}}}*/
+// ExecWait - Fancy waitpid                                            /*{{{*/
+// ---------------------------------------------------------------------
+/* Waits for the given sub process. If Reap is set the no errors are 
+   generated. Otherwise a failed subprocess will generate a proper descriptive
+   message */
+bool ExecWait(int Pid,const char *Name,bool Reap)
+{
+   if (Pid <= 1)
+      return true;
+   
+   // Wait and collect the error code
+   int Status;
+   while (waitpid(Pid,&Status,0) != Pid)
+   {
+      if (errno == EINTR)
+        continue;
+
+      if (Reap == true)
+        return false;
+      
+      return _error->Error("Waited, for %s but it wasn't there",Name);
+   }
+
+   
+   // Check for an error code.
+   if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
+   {
+      if (Reap == true)
+        return false;
+      if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
+        return _error->Error("Sub-process %s recieved a segmentation fault.",Name);
+
+      if (WIFEXITED(Status) != 0)
+        return _error->Error("Sub-process %s returned an error code (%u)",Name,WEXITSTATUS(Status));
+      
+      return _error->Error("Sub-process %s exited unexpectedly",Name);
+   }      
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+// FileFd::Open - Open a file                                          /*{{{*/
+// ---------------------------------------------------------------------
+/* The most commonly used open mode combinations are given with Mode */
+bool FileFd::Open(string FileName,OpenMode Mode, unsigned long Perms)
+{
+   Close();
+   Flags = AutoClose;
+   switch (Mode)
+   {
+      case ReadOnly:
+      iFd = open(FileName.c_str(),O_RDONLY);
+      break;
+      
+      case WriteEmpty:
+      {
+        struct stat Buf;
+        if (stat(FileName.c_str(),&Buf) == 0 && S_ISLNK(Buf.st_mode))
+           unlink(FileName.c_str());
+        iFd = open(FileName.c_str(),O_RDWR | O_CREAT | O_TRUNC,Perms);
+        break;
+      }
+      
+      case WriteExists:
+      iFd = open(FileName.c_str(),O_RDWR);
+      break;
+
+      case WriteAny:
+      iFd = open(FileName.c_str(),O_RDWR | O_CREAT,Perms);
+      break;      
+   }  
+
+   if (iFd < 0)
+      return _error->Errno("open","Could not open file %s",FileName.c_str());
+   
+   this->FileName = FileName;
+   SetCloseExec(iFd,true);
+   return true;
+}
+                                                                       /*}}}*/
+// FileFd::~File - Closes the file                                     /*{{{*/
+// ---------------------------------------------------------------------
+/* If the proper modes are selected then we close the Fd and possibly
+   unlink the file on error. */
+FileFd::~FileFd()
+{
+   Close();
+}
+                                                                       /*}}}*/
+// FileFd::Read - Read a bit of the file                               /*{{{*/
+// ---------------------------------------------------------------------
+/* We are carefull to handle interruption by a signal while reading 
+   gracefully. */
+bool FileFd::Read(void *To,unsigned long Size,bool AllowEof)
+{
+   int Res;
+   errno = 0;
+   do
+   {
+      Res = read(iFd,To,Size);
+      if (Res < 0 && errno == EINTR)
+        continue;
+      if (Res < 0)
+      {
+        Flags |= Fail;
+        return _error->Errno("read","Read error");
+      }
+      
+      To = (char *)To + Res;
+      Size -= Res;
+   }
+   while (Res > 0 && Size > 0);
+   
+   if (Size == 0)
+      return true;
+   
+   // Eof handling
+   if (AllowEof == true)
+   {
+      Flags |= HitEof;
+      return true;
+   }
+   
+   Flags |= Fail;
+   return _error->Error("read, still have %u to read but none left",Size);
+}
+                                                                       /*}}}*/
+// FileFd::Write - Write to the file                                   /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool FileFd::Write(const void *From,unsigned long Size)
+{
+   int Res;
+   errno = 0;
+   do
+   {
+      Res = write(iFd,From,Size);
+      if (Res < 0 && errno == EINTR)
+        continue;
+      if (Res < 0)
+      {
+        Flags |= Fail;
+        return _error->Errno("write","Write error");
+      }
+      
+      From = (char *)From + Res;
+      Size -= Res;
+   }
+   while (Res > 0 && Size > 0);
+   
+   if (Size == 0)
+      return true;
+   
+   Flags |= Fail;
+   return _error->Error("write, still have %u to write but couldn't",Size);
+}
+                                                                       /*}}}*/
+// FileFd::Seek - Seek in the file                                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool FileFd::Seek(unsigned long To)
+{
+   if (lseek(iFd,To,SEEK_SET) != (signed)To)
+   {
+      Flags |= Fail;
+      return _error->Error("Unable to seek to %u",To);
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// FileFd::Skip - Seek in the file                                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool FileFd::Skip(unsigned long Over)
+{
+   if (lseek(iFd,Over,SEEK_CUR) < 0)
+   {
+      Flags |= Fail;
+      return _error->Error("Unable to seek ahead %u",Over);
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// FileFd::Truncate - Truncate the file                                /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool FileFd::Truncate(unsigned long To)
+{
+   if (ftruncate(iFd,To) != 0)
+   {
+      Flags |= Fail;
+      return _error->Error("Unable to truncate to %u",To);
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// FileFd::Tell - Current seek position                                        /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+unsigned long FileFd::Tell()
+{
+   off_t Res = lseek(iFd,0,SEEK_CUR);
+   if (Res == (off_t)-1)
+      _error->Errno("lseek","Failed to determine the current file position");
+   return Res;
+}
+                                                                       /*}}}*/
+// FileFd::Size - Return the size of the file                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+unsigned long FileFd::Size()
+{
+   struct stat Buf;
+   if (fstat(iFd,&Buf) != 0)
+      return _error->Errno("fstat","Unable to determine the file size");
+   return Buf.st_size;
+}
+                                                                       /*}}}*/
+// FileFd::Close - Close the file if the close flag is set             /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool FileFd::Close()
+{
+   bool Res = true;
+   if ((Flags & AutoClose) == AutoClose)
+      if (iFd >= 0 && close(iFd) != 0)
+        Res &= _error->Errno("close","Problem closing the file");
+   iFd = -1;
+   
+   if ((Flags & Fail) == Fail && (Flags & DelOnFail) == DelOnFail &&
+       FileName.empty() == false)
+      if (unlink(FileName.c_str()) != 0)
+        Res &= _error->Warning("unlnk","Problem unlinking the file");
+   return Res;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/fileutl.h b/tools/dsync-0.0/libdsync/contrib/fileutl.h
new file mode 100644 (file)
index 0000000..1be6009
--- /dev/null
@@ -0,0 +1,90 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: fileutl.h,v 1.3 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+   
+   File Utilities
+   
+   CopyFile - Buffered copy of a single file
+   GetLock - dpkg compatible lock file manipulation (fcntl)
+   FileExists - Returns true if the file exists
+   SafeGetCWD - Returns the CWD in a string with overrun protection 
+   
+   The file class is a handy abstraction for various functions+classes
+   that need to accept filenames.
+   
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Jason Gunthorpe.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef PKGLIB_FILEUTL_H
+#define PKGLIB_FILEUTL_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/fileutl.h"
+#endif 
+
+#include <string>
+
+using namespace std;
+
+class FileFd
+{
+   protected:
+   int iFd;
+   enum LocalFlags {AutoClose = (1<<0),Fail = (1<<1),DelOnFail = (1<<2),
+                    HitEof = (1<<3)};
+   unsigned long Flags;
+   string FileName;
+   
+   public:
+   enum OpenMode {ReadOnly,WriteEmpty,WriteExists,WriteAny};
+   
+   bool Read(void *To,unsigned long Size,bool AllowEof = false);
+   bool Write(const void *From,unsigned long Size);
+   bool Seek(unsigned long To);
+   bool Skip(unsigned long To);
+   bool Truncate(unsigned long To);
+   unsigned long Tell();
+   unsigned long Size();
+   bool Open(string FileName,OpenMode Mode,unsigned long Perms = 0666);
+   bool Close();
+
+   // Simple manipulators
+   inline int Fd() {return iFd;};
+   inline void Fd(int fd) {iFd = fd;};
+   inline bool IsOpen() {return iFd >= 0;};
+   inline bool Failed() {return (Flags & Fail) == Fail;};
+   inline void EraseOnFailure() {Flags |= DelOnFail;};
+   inline void OpFail() {Flags |= Fail;};
+   inline bool Eof() {return (Flags & HitEof) == HitEof;};
+   inline string &Name() {return FileName;};
+   
+   FileFd(string FileName,OpenMode Mode,unsigned long Perms = 0666) : iFd(-1), 
+            Flags(0) 
+   {
+      Open(FileName,Mode,Perms);
+   };
+   FileFd(int Fd = -1) : iFd(Fd), Flags(AutoClose) {};
+   FileFd(int Fd,bool) : iFd(Fd), Flags(0) {};
+   virtual ~FileFd();
+};
+
+bool CopyFile(FileFd &From,FileFd &To);
+int GetLock(string File,bool Errors = true);
+bool FileExists(string File);
+string SafeGetCWD();
+void SetCloseExec(int Fd,bool Close);
+void SetNonBlock(int Fd,bool Block);
+bool WaitFd(int Fd,bool write = false,unsigned long timeout = 0);
+int ExecFork();
+bool ExecWait(int Pid,const char *Name,bool Reap = false);
+
+// File string manipulators
+string flNotDir(string File);
+string flNotFile(string File);
+string flNoLink(string File);
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/md4.cc b/tools/dsync-0.0/libdsync/contrib/md4.cc
new file mode 100644 (file)
index 0000000..32e0ddb
--- /dev/null
@@ -0,0 +1,182 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: md4.cc,v 1.4 1999/11/17 05:59:29 jgg Exp $
+/* ######################################################################
+   
+   MD4Sum - MD4 Message Digest Algorithm.
+
+   This code implements the MD4 message-digest algorithm. See RFC 1186.
+
+   Ripped shamelessly from RSync which ripped it shamelessly from Samba.
+   Code is covered under the GPL >=2 and has been changed to have a C++
+   interface and use the local configuration stuff.
+
+   Copyright (C) Andrew Tridgell 1997-1998.
+   
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include Files                                                       /*{{{*/
+#include <dsync/md4.h>
+
+#include <string.h>
+#include <inttypes.h>
+#include <config.h>
+                                                                       /*}}}*/
+
+// byteSwap - Swap bytes in a buffer                                   /*{{{*/
+// ---------------------------------------------------------------------
+/* Swap n 32 bit longs in given buffer */
+#ifdef WORDS_BIGENDIAN
+static void byteSwap(uint32_t *buf, unsigned words)
+{
+   uint8_t *p = (uint8_t *)buf;
+   
+   do 
+   {
+      *buf++ = (uint32_t)((unsigned)p[3] << 8 | p[2]) << 16 |
+        ((unsigned)p[1] << 8 | p[0]);
+      p += 4;
+   } while (--words);
+}
+#else
+#define byteSwap(buf,words)
+#endif
+                                                                       /*}}}*/
+// InitMD4 - Init the MD4 buffer                                       /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void InitMD4(unsigned char MD4[16])
+{
+   uint32_t X[4] = {0x67452301,0xefcdab89,0x98badcfe,0x10325476};
+   byteSwap(X,4);
+   memcpy(MD4,X,16);
+}
+                                                                       /*}}}*/
+// ComputeMD4 - Compute the MD4 hash of a buffer                       /*{{{*/
+// ---------------------------------------------------------------------
+/* The buffer *must* be an even multiple of 64 bytes long. The resulting
+   hash is placed in the output buffer in */
+#define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z)))
+#define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)))
+#define H(X,Y,Z) ((X)^(Y)^(Z))
+#define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s))))
+
+#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s)
+#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s)
+#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s)
+
+void ComputeMD4(unsigned char MD4[16],unsigned char const *Start,
+               unsigned const char *End)
+{
+   uint32_t X[16];
+   uint32_t A,B,C,D;
+
+   // Prepare the sum state
+   memcpy(X,MD4,16);
+   byteSwap(X,4);
+   A = X[0];
+   B = X[1];
+   C = X[2];
+   D = X[3];
+   
+   for (; End - Start >= 64; Start += 64)
+   {      
+      uint32_t AA, BB, CC, DD;
+      
+      memcpy(X,Start,sizeof(X));
+      byteSwap(X,16);
+        
+      AA = A; BB = B; CC = C; DD = D;
+      
+      ROUND1(A,B,C,D,  0,  3);  ROUND1(D,A,B,C,  1,  7);  
+      ROUND1(C,D,A,B,  2, 11);  ROUND1(B,C,D,A,  3, 19);
+      ROUND1(A,B,C,D,  4,  3);  ROUND1(D,A,B,C,  5,  7);  
+      ROUND1(C,D,A,B,  6, 11);  ROUND1(B,C,D,A,  7, 19);
+      ROUND1(A,B,C,D,  8,  3);  ROUND1(D,A,B,C,  9,  7);  
+      ROUND1(C,D,A,B, 10, 11);  ROUND1(B,C,D,A, 11, 19);
+      ROUND1(A,B,C,D, 12,  3);  ROUND1(D,A,B,C, 13,  7);  
+      ROUND1(C,D,A,B, 14, 11);  ROUND1(B,C,D,A, 15, 19);       
+      
+      ROUND2(A,B,C,D,  0,  3);  ROUND2(D,A,B,C,  4,  5);  
+      ROUND2(C,D,A,B,  8,  9);  ROUND2(B,C,D,A, 12, 13);
+      ROUND2(A,B,C,D,  1,  3);  ROUND2(D,A,B,C,  5,  5);  
+      ROUND2(C,D,A,B,  9,  9);  ROUND2(B,C,D,A, 13, 13);
+      ROUND2(A,B,C,D,  2,  3);  ROUND2(D,A,B,C,  6,  5);  
+      ROUND2(C,D,A,B, 10,  9);  ROUND2(B,C,D,A, 14, 13);
+      ROUND2(A,B,C,D,  3,  3);  ROUND2(D,A,B,C,  7,  5);  
+      ROUND2(C,D,A,B, 11,  9);  ROUND2(B,C,D,A, 15, 13);
+      
+      ROUND3(A,B,C,D,  0,  3);  ROUND3(D,A,B,C,  8,  9);  
+      ROUND3(C,D,A,B,  4, 11);  ROUND3(B,C,D,A, 12, 15);
+      ROUND3(A,B,C,D,  2,  3);  ROUND3(D,A,B,C, 10,  9);  
+      ROUND3(C,D,A,B,  6, 11);  ROUND3(B,C,D,A, 14, 15);
+      ROUND3(A,B,C,D,  1,  3);  ROUND3(D,A,B,C,  9,  9);  
+      ROUND3(C,D,A,B,  5, 11);  ROUND3(B,C,D,A, 13, 15);
+      ROUND3(A,B,C,D,  3,  3);  ROUND3(D,A,B,C, 11,  9);  
+      ROUND3(C,D,A,B,  7, 11);  ROUND3(B,C,D,A, 15, 15);
+      
+      A += AA; 
+      B += BB; 
+      C += CC; 
+      D += DD;
+   }
+   X[0] = A;
+   X[1] = B;
+   X[2] = C;
+   X[3] = D;
+   
+   byteSwap(X,4);
+   memcpy(MD4,X,16);
+}
+                                                                       /*}}}*/
+// ComputeMD4Final - Finalize the MD4, length and pad                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This does the final round of MD4, Start->End will be padded to be
+   congruent to 0 mod 64 and TotalLen appended. */
+void ComputeMD4Final(unsigned char MD4[16],unsigned char const *Start,
+                    unsigned char const *End,unsigned long TotalLen)
+{
+   if (End - Start >= 64)
+   {
+      ComputeMD4(MD4,Start,End - ((End - Start)%64));
+      Start = End - ((End - Start)%64);
+   }
+   
+   uint8_t Buf[128];
+   uint32_t Len = TotalLen*8;
+   
+   // Create the partial end buffer, padded to be 448%512 bits long
+   memset(Buf,0,128);
+   if (Start != End) 
+      memcpy(Buf,Start,End - Start);   
+   Buf[End-Start] = 0x80;
+   
+   // Append the 32 bit length into the 64 bit field
+   if (End-Start <= 55) 
+   {      
+      memcpy(Buf+56,&Len,sizeof(Len));
+      byteSwap((uint32_t *)(Buf+56),1);
+      ComputeMD4(MD4,Buf,Buf+64);
+   }
+   else 
+   {
+      memcpy(Buf+120,&Len,sizeof(Len));
+      byteSwap((uint32_t *)(Buf+120),1);
+      ComputeMD4(MD4,Buf,Buf+128);
+   }   
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/md4.h b/tools/dsync-0.0/libdsync/contrib/md4.h
new file mode 100644 (file)
index 0000000..d2b2e90
--- /dev/null
@@ -0,0 +1,21 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: md4.h,v 1.2 1999/11/17 04:07:17 jgg Exp $
+/* ######################################################################
+   
+   MD4 - MD4 Message Digest Algorithm.
+   
+   This is a simple function to compute the MD4 of 
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_MD4_H
+#define DSYNC_MD4_H
+
+void InitMD4(unsigned char MD4[16]);
+void ComputeMD4(unsigned char MD4[16],unsigned char const *Start,
+               unsigned const char *End);
+void ComputeMD4Final(unsigned char MD4[16],unsigned char const *Start,
+                    unsigned char const *End,unsigned long TotalLen);
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/md5.cc b/tools/dsync-0.0/libdsync/contrib/md5.cc
new file mode 100644 (file)
index 0000000..4066ae7
--- /dev/null
@@ -0,0 +1,357 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: md5.cc,v 1.5 1999/11/17 04:13:49 jgg Exp $
+/* ######################################################################
+   
+   MD5Sum - MD5 Message Digest Algorithm.
+
+   This code implements the MD5 message-digest algorithm. The algorithm is 
+   due to Ron Rivest.  This code was written by Colin Plumb in 1993, no 
+   copyright is claimed. This code is in the public domain; do with it what 
+   you wish.
+   Equivalent code is available from RSA Data Security, Inc. This code has 
+   been tested against that, and is equivalent, except that you don't need to 
+   include two pages of legalese with every copy.
+
+   To compute the message digest of a chunk of bytes, instantiate the class,
+   and repeatedly call one of the Add() members. When finished the Result 
+   method will return the Hash and finalize the value.
+   
+   Changed so as no longer to depend on Colin Plumb's `usual.h' header
+   definitions; now uses stuff from dpkg's config.h.
+    - Ian Jackson <ijackson@nyx.cs.du.edu>.
+   
+   Changed into a C++ interface and made work with APT's config.h.
+    - Jason Gunthorpe <jgg@gpu.srv.ualberta.ca>
+   
+   Still in the public domain.
+
+   The classes use arrays of char that are a specific size. We cast those
+   arrays to uint8_t's and go from there. This allows us to advoid using
+   the uncommon inttypes.h in a public header or internally newing memory.
+   In theory if C9x becomes nicely accepted
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include Files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/md5.h"
+#endif
+
+#include <dsync/md5.h>
+#include <dsync/strutl.h>
+
+#include <string.h>
+#include <system.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <config.h>
+                                                                       /*}}}*/
+
+// byteSwap - Swap bytes in a buffer                                   /*{{{*/
+// ---------------------------------------------------------------------
+/* Swap n 32 bit longs in given buffer */
+#ifdef WORDS_BIGENDIAN
+static void byteSwap(uint32_t *buf, unsigned words)
+{
+   uint8_t *p = (uint8_t *)buf;
+   
+   do 
+   {
+      *buf++ = (uint32_t)((unsigned)p[3] << 8 | p[2]) << 16 |
+        ((unsigned)p[1] << 8 | p[0]);
+      p += 4;
+   } while (--words);
+}
+#else
+#define byteSwap(buf,words)
+#endif
+                                                                       /*}}}*/
+// MD5Transform - Alters an existing MD5 hash                          /*{{{*/
+// ---------------------------------------------------------------------
+/* The core of the MD5 algorithm, this alters an existing MD5 hash to
+   reflect the addition of 16 longwords of new data. Add blocks
+   the data and converts bytes into longwords for this routine. */
+
+// The four core functions - F1 is optimized somewhat
+// #define F1(x, y, z) (x & y | ~x & z)
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+// This is the central step in the MD5 algorithm.
+#define MD5STEP(f,w,x,y,z,in,s) \
+        (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
+
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+   register uint32_t a, b, c, d;
+   
+   a = buf[0];
+   b = buf[1];
+   c = buf[2];
+   d = buf[3];
+   
+   MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+   MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+   MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+   MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+   MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+   MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+   MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+   MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+   MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+   MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+   MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+   MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+   MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+   MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+   MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+   MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+   MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+   MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+   MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+   MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+   MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+   MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+   MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+   MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+   MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+   MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+   MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+   MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+   MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+   MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+   MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+   MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+   
+   MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+   MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+   MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+   MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+   MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+   MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+   MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+   MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+   MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+   MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+   MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+   MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+   MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+   MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+   MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+   MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+   
+   MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+   MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+   MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+   MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+   MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+   MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+   MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+   MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+   MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+   MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+   MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+   MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+   MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+   MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+   MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+   MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+   
+   buf[0] += a;
+   buf[1] += b;
+   buf[2] += c;
+   buf[3] += d;
+}
+                                                                       /*}}}*/
+// MD5SumValue::MD5SumValue - Constructs the summation from a string   /*{{{*/
+// ---------------------------------------------------------------------
+/* The string form of a MD5 is a 32 character hex number */
+MD5SumValue::MD5SumValue(string Str)
+{
+   memset(Sum,0,sizeof(Sum));
+   Set(Str);
+}
+                                                                       /*}}}*/
+// MD5SumValue::MD5SumValue - Default constructor                      /*{{{*/
+// ---------------------------------------------------------------------
+/* Sets the value to 0 */
+MD5SumValue::MD5SumValue()
+{
+   memset(Sum,0,sizeof(Sum));
+}
+                                                                       /*}}}*/
+// MD5SumValue::Set - Set the sum from a string                                /*{{{*/
+// ---------------------------------------------------------------------
+/* Converts the hex string into a set of chars */
+bool MD5SumValue::Set(string Str)
+{
+   return Hex2Num(Str.c_str(),Str.c_str()+strlen(Str.c_str()),Sum,sizeof(Sum));
+}
+                                                                       /*}}}*/
+// MD5SumValue::Value - Convert the number into a string               /*{{{*/
+// ---------------------------------------------------------------------
+/* Converts the set of chars into a hex string in lower case */
+string MD5SumValue::Value() const
+{
+   char Conv[16] = {'0','1','2','3','4','5','6','7','8','9','a','b',
+                    'c','d','e','f'};
+   char Result[33];
+   Result[32] = 0;
+   
+   // Convert each char into two letters
+   int J = 0;
+   int I = 0;
+   for (; I != 32; J++, I += 2)
+   {
+      Result[I] = Conv[Sum[J] >> 4];
+      Result[I + 1] = Conv[Sum[J] & 0xF];
+   } 
+
+   return string(Result);
+}
+                                                                       /*}}}*/
+// MD5SumValue::operator == - Comparitor                               /*{{{*/
+// ---------------------------------------------------------------------
+/* Call memcmp on the buffer */
+bool MD5SumValue::operator ==(const MD5SumValue &rhs) const
+{
+   return memcmp(Sum,rhs.Sum,sizeof(Sum)) == 0;
+}
+                                                                       /*}}}*/
+// MD5Summation::MD5Summation - Initialize the summer                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This assigns the deep magic initial values */
+MD5Summation::MD5Summation()
+{
+   uint32_t *buf = (uint32_t *)Buf;
+   uint32_t *bytes = (uint32_t *)Bytes;
+   
+   buf[0] = 0x67452301;
+   buf[1] = 0xefcdab89;
+   buf[2] = 0x98badcfe;
+   buf[3] = 0x10325476;
+   
+   bytes[0] = 0;
+   bytes[1] = 0;
+   Done = false;
+}
+                                                                       /*}}}*/
+// MD5Summation::Add - 'Add' a data set to the hash                    /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool MD5Summation::Add(const unsigned char *data,unsigned long len)
+{
+   if (Done == true)
+      return false;
+
+   uint32_t *buf = (uint32_t *)Buf;
+   uint32_t *bytes = (uint32_t *)Bytes;
+   uint32_t *in = (uint32_t *)In;
+
+   // Update byte count and carry (this could be done with a long long?)
+   uint32_t t = bytes[0];
+   if ((bytes[0] = t + len) < t)
+      bytes[1]++;      
+
+   // Space available (at least 1)
+   t = 64 - (t & 0x3f);        
+   if (t > len) 
+   {
+      memcpy((unsigned char *)in + 64 - t,data,len);
+      return true;
+   }
+
+   // First chunk is an odd size
+   memcpy((unsigned char *)in + 64 - t,data,t);
+   byteSwap(in, 16);
+   MD5Transform(buf,in);
+   data += t;
+   len -= t;
+   
+   // Process data in 64-byte chunks
+   while (len >= 64)
+   {
+      memcpy(in,data,64);
+      byteSwap(in,16);
+      MD5Transform(buf,in);
+      data += 64;
+      len -= 64;
+   }
+
+   // Handle any remaining bytes of data.
+   memcpy(in,data,len);
+
+   return true;   
+}
+                                                                       /*}}}*/
+// MD5Summation::AddFD - Add the contents of a FD to the hash          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool MD5Summation::AddFD(int Fd,unsigned long Size)
+{
+   unsigned char Buf[64*64];
+   int Res = 0;
+   while (Size != 0)
+   {
+      Res = read(Fd,Buf,MIN(Size,sizeof(Buf)));
+      if (Res < 0 || (unsigned)Res != MIN(Size,sizeof(Buf)))
+        return false;
+      Size -= Res;
+      Add(Buf,Res);
+   }
+   return true;
+}
+                                                                       /*}}}*/
+// MD5Summation::Result - Returns the value of the sum                 /*{{{*/
+// ---------------------------------------------------------------------
+/* Because this must add in the last bytes of the series it prevents anyone
+   from calling add after. */
+MD5SumValue MD5Summation::Result()
+{
+   uint32_t *buf = (uint32_t *)Buf;
+   uint32_t *bytes = (uint32_t *)Bytes;
+   uint32_t *in = (uint32_t *)In;
+   
+   if (Done == false)
+   {
+      // Number of bytes in In
+      int count = bytes[0] & 0x3f;     
+      unsigned char *p = (unsigned char *)in + count;
+      
+      // Set the first char of padding to 0x80.  There is always room.
+      *p++ = 0x80;
+      
+      // Bytes of padding needed to make 56 bytes (-8..55)
+      count = 56 - 1 - count;
+      
+      // Padding forces an extra block 
+      if (count < 0) 
+      {
+        memset(p,0,count + 8);
+        byteSwap(in, 16);
+        MD5Transform(buf,in);
+        p = (unsigned char *)in;
+        count = 56;
+      }
+      
+      memset(p, 0, count);
+      byteSwap(in, 14);
+      
+      // Append length in bits and transform
+      in[14] = bytes[0] << 3;
+      in[15] = bytes[1] << 3 | bytes[0] >> 29;
+      MD5Transform(buf,in);   
+      byteSwap(buf,4);
+      Done = true;
+   }
+   
+   MD5SumValue V;
+   memcpy(V.Sum,buf,16);
+   return V;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/md5.h b/tools/dsync-0.0/libdsync/contrib/md5.h
new file mode 100644 (file)
index 0000000..6fb39ad
--- /dev/null
@@ -0,0 +1,75 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: md5.h,v 1.4 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+   
+   MD5SumValue - Storage for a MD5Sum
+   MD5Summation - MD5 Message Digest Algorithm.
+   
+   This is a C++ interface to a set of MD5Sum functions. The class can
+   store a MD5Sum in 16 bytes of memory.
+   
+   A MD5Sum is used to generate a (hopefully) unique 16 byte number for a
+   block of data. This can be used to gaurd against corruption of a file.
+   MD5 should not be used for tamper protection, use SHA or something more
+   secure.
+   
+   There are two classes because computing a MD5 is not a continual 
+   operation unless 64 byte blocks are used. Also the summation requires an
+   extra 18*4 bytes to operate.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef APTPKG_MD5_H
+#define APTPKG_MD5_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/md5.h"
+#endif 
+
+#include <string>
+using namespace std;
+
+class MD5Summation;
+
+class MD5SumValue
+{
+   friend class MD5Summation;
+   unsigned char Sum[4*4];
+   
+   public:
+
+   // Accessors
+   bool operator ==(const MD5SumValue &rhs) const; 
+   string Value() const;
+   inline void Value(unsigned char S[16]) 
+         {for (int I = 0; I != sizeof(Sum); I++) S[I] = Sum[I];};
+   inline operator string() const {return Value();};
+   bool Set(string Str);
+   inline void Set(unsigned char S[16]) 
+         {for (int I = 0; I != sizeof(Sum); I++) Sum[I] = S[I];};
+
+   MD5SumValue(string Str);
+   MD5SumValue();
+};
+
+class MD5Summation
+{
+   unsigned char Buf[4*4];
+   unsigned char Bytes[2*4];
+   unsigned char In[16*4];
+   bool Done;
+   
+   public:
+
+   bool Add(const unsigned char *Data,unsigned long Size);
+   inline bool Add(const char *Data) {return Add((unsigned char *)Data,strlen(Data));};
+   bool AddFD(int Fd,unsigned long Size);
+   inline bool Add(const unsigned char *Beg,const unsigned char *End) 
+                  {return Add(Beg,End-Beg);};
+   MD5SumValue Result();
+   
+   MD5Summation();
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/mmap.cc b/tools/dsync-0.0/libdsync/contrib/mmap.cc
new file mode 100644 (file)
index 0000000..2d25ce8
--- /dev/null
@@ -0,0 +1,279 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: mmap.cc,v 1.3 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+   
+   MMap Class - Provides 'real' mmap or a faked mmap using read().
+
+   MMap cover class.
+
+   Some broken versions of glibc2 (libc6) have a broken definition
+   of mmap that accepts a char * -- all other systems (and libc5) use
+   void *. We can't safely do anything here that would be portable, so
+   libc6 generates warnings -- which should be errors, g++ isn't properly
+   strict.
+   
+   The configure test notes that some OS's have broken private mmap's
+   so on those OS's we can't use mmap. This means we have to use
+   configure to test mmap and can't rely on the POSIX
+   _POSIX_MAPPED_FILES test.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include Files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/mmap.h"
+#endif 
+
+#define _BSD_SOURCE
+#include <dsync/mmap.h>
+#include <dsync/error.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+                                                                       /*}}}*/
+
+// MMap::MMap - Constructor                                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+MMap::MMap(FileFd &F,unsigned long Flags) : Flags(Flags), iSize(0),
+                     Base(0)
+{
+   if ((Flags & NoImmMap) != NoImmMap)
+      Map(F);
+}
+                                                                       /*}}}*/
+// MMap::MMap - Constructor                                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+MMap::MMap(unsigned long Flags) : Flags(Flags), iSize(0),
+                     Base(0)
+{
+}
+                                                                       /*}}}*/
+// MMap::~MMap - Destructor                                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+MMap::~MMap()
+{
+   Close();
+}
+                                                                       /*}}}*/
+// MMap::Map - Perform the mapping                                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool MMap::Map(FileFd &Fd)
+{
+   iSize = Fd.Size();
+   
+   // Set the permissions.
+   int Prot = PROT_READ;
+   int Map = MAP_SHARED;
+   if ((Flags & ReadOnly) != ReadOnly)
+      Prot |= PROT_WRITE;
+   if ((Flags & Public) != Public)
+      Map = MAP_PRIVATE;
+   
+   if (iSize == 0)
+      return _error->Error("Can't mmap an empty file");
+   
+   // Map it.
+   Base = mmap(0,iSize,Prot,Map,Fd.Fd(),0);
+   if (Base == (void *)-1)
+      return _error->Errno("mmap","Couldn't make mmap of %u bytes",iSize);
+
+   return true;
+}
+                                                                       /*}}}*/
+// MMap::Close - Close the map                                         /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool MMap::Close(bool DoSync)
+{
+   if ((Flags & UnMapped) == UnMapped || Base == 0 || iSize == 0)
+      return true;
+   
+   if (DoSync == true)
+      Sync();
+   
+   if (munmap((char *)Base,iSize) != 0)
+      _error->Warning("Unable to munmap");
+   
+   iSize = 0;
+   return true;
+}
+                                                                       /*}}}*/
+// MMap::Sync - Syncronize the map with the disk                       /*{{{*/
+// ---------------------------------------------------------------------
+/* This is done in syncronous mode - the docs indicate that this will 
+   not return till all IO is complete */
+bool MMap::Sync()
+{   
+   if ((Flags & UnMapped) == UnMapped)
+      return true;
+   
+#ifdef _POSIX_SYNCHRONIZED_IO   
+   if ((Flags & ReadOnly) != ReadOnly)
+      if (msync((char *)Base,iSize,MS_SYNC) != 0)
+        return _error->Errno("msync","Unable to write mmap");
+#endif   
+   return true;
+}
+                                                                       /*}}}*/
+// MMap::Sync - Syncronize a section of the file to disk               /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool MMap::Sync(unsigned long Start,unsigned long Stop)
+{
+   if ((Flags & UnMapped) == UnMapped)
+      return true;
+   
+#ifdef _POSIX_SYNCHRONIZED_IO
+   unsigned long PSize = sysconf(_SC_PAGESIZE);
+   if ((Flags & ReadOnly) != ReadOnly)
+      if (msync((char *)Base+(int)(Start/PSize)*PSize,Stop - Start,MS_SYNC) != 0)
+        return _error->Errno("msync","Unable to write mmap");
+#endif   
+   return true;
+}
+                                                                       /*}}}*/
+
+// DynamicMMap::DynamicMMap - Constructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+DynamicMMap::DynamicMMap(FileFd &F,unsigned long Flags,unsigned long WorkSpace) : 
+             MMap(F,Flags | NoImmMap), Fd(&F), WorkSpace(WorkSpace)
+{
+   if (_error->PendingError() == true)
+      return;
+   
+   unsigned long EndOfFile = Fd->Size();
+   Fd->Seek(WorkSpace);
+   char C = 0;
+   Fd->Write(&C,sizeof(C));
+   Map(F);
+   iSize = EndOfFile;
+}
+                                                                       /*}}}*/
+// DynamicMMap::DynamicMMap - Constructor for a non-file backed map    /*{{{*/
+// ---------------------------------------------------------------------
+/* This is just a fancy malloc really.. */
+DynamicMMap::DynamicMMap(unsigned long Flags,unsigned long WorkSpace) :
+             MMap(Flags | NoImmMap | UnMapped), Fd(0), WorkSpace(WorkSpace)
+{
+   if (_error->PendingError() == true)
+      return;
+   
+   Base = new unsigned char[WorkSpace];
+   iSize = 0;
+}
+                                                                       /*}}}*/
+// DynamicMMap::~DynamicMMap - Destructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* We truncate the file to the size of the memory data set */
+DynamicMMap::~DynamicMMap()
+{
+   if (Fd == 0)
+   {
+      delete [] (unsigned char *)Base;
+      return;
+   }
+   
+   unsigned long EndOfFile = iSize;
+   Sync();
+   iSize = WorkSpace;
+   Close(false);
+   ftruncate(Fd->Fd(),EndOfFile);
+   Fd->Close();
+}  
+                                                                       /*}}}*/
+// DynamicMMap::RawAllocate - Allocate a raw chunk of unaligned space  /*{{{*/
+// ---------------------------------------------------------------------
+/* This allocates a block of memory aligned to the given size */
+unsigned long DynamicMMap::RawAllocate(unsigned long Size,unsigned long Aln)
+{
+   unsigned long Result = iSize;
+   if (Aln != 0)
+      Result += Aln - (iSize%Aln);
+   
+   iSize = Result + Size;
+   
+   // Just in case error check
+   if (Result + Size > WorkSpace)
+   {
+      _error->Error("Dynamic MMap ran out of room");
+      return 0;
+   }
+
+   return Result;
+}
+                                                                       /*}}}*/
+// DynamicMMap::Allocate - Pooled aligned allocation                   /*{{{*/
+// ---------------------------------------------------------------------
+/* This allocates an Item of size ItemSize so that it is aligned to its
+   size in the file. */
+unsigned long DynamicMMap::Allocate(unsigned long ItemSize)
+{   
+   // Look for a matching pool entry
+   Pool *I;
+   Pool *Empty = 0;
+   for (I = Pools; I != Pools + PoolCount; I++)
+   {
+      if (I->ItemSize == 0)
+        Empty = I;
+      if (I->ItemSize == ItemSize)
+        break;
+   }
+
+   // No pool is allocated, use an unallocated one
+   if (I == Pools + PoolCount)
+   {
+      // Woops, we ran out, the calling code should allocate more.
+      if (Empty == 0)
+      {
+        _error->Error("Ran out of allocation pools");
+        return 0;
+      }
+      
+      I = Empty;
+      I->ItemSize = ItemSize;
+      I->Count = 0;
+   }
+   
+   // Out of space, allocate some more
+   if (I->Count == 0)
+   {
+      I->Count = 20*1024/ItemSize;
+      I->Start = RawAllocate(I->Count*ItemSize,ItemSize);
+   }   
+
+   I->Count--;
+   unsigned long Result = I->Start;
+   I->Start += ItemSize;  
+   return Result/ItemSize;
+}
+                                                                       /*}}}*/
+// DynamicMMap::WriteString - Write a string to the file               /*{{{*/
+// ---------------------------------------------------------------------
+/* Strings are not aligned to anything */
+unsigned long DynamicMMap::WriteString(const char *String,
+                                      unsigned long Len)
+{
+   unsigned long Result = iSize;
+   // Just in case error check
+   if (Result + Len > WorkSpace)
+   {
+      _error->Error("Dynamic MMap ran out of room");
+      return 0;
+   }   
+   
+   if (Len == (unsigned long)-1)
+      Len = strlen(String);
+   iSize += Len + 1;
+   memcpy((char *)Base + Result,String,Len);
+   ((char *)Base)[Result + Len] = 0;
+   return Result;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/mmap.h b/tools/dsync-0.0/libdsync/contrib/mmap.h
new file mode 100644 (file)
index 0000000..d4a2580
--- /dev/null
@@ -0,0 +1,103 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: mmap.h,v 1.2 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+   
+   MMap Class - Provides 'real' mmap or a faked mmap using read().
+
+   The purpose of this code is to provide a generic way for clients to
+   access the mmap function. In enviroments that do not support mmap
+   from file fd's this function will use read and normal allocated 
+   memory.
+   
+   Writing to a public mmap will always fully comit all changes when the 
+   class is deleted. Ie it will rewrite the file, unless it is readonly
+
+   The DynamicMMap class is used to help the on-disk data structure 
+   generators. It provides a large allocated workspace and members
+   to allocate space from the workspace in an effecient fashion.
+   
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Jason Gunthorpe.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef PKGLIB_MMAP_H
+#define PKGLIB_MMAP_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/mmap.h"
+#endif
+
+#include <string>
+#include <dsync/fileutl.h>
+
+/* This should be a 32 bit type, larger tyes use too much ram and smaller
+   types are too small. Where ever possible 'unsigned long' should be used
+   instead of this internal type */
+typedef unsigned int map_ptrloc;
+
+class MMap
+{
+   protected:
+   
+   unsigned long Flags;
+   unsigned long iSize;
+   void *Base;
+
+   bool Map(FileFd &Fd);
+   bool Close(bool DoSync = true);
+   
+   public:
+
+   enum OpenFlags {NoImmMap = (1<<0),Public = (1<<1),ReadOnly = (1<<2),
+                   UnMapped = (1<<3)};
+      
+   // Simple accessors
+   inline operator void *() {return Base;};
+   inline void *Data() {return Base;}; 
+   inline unsigned long Size() {return iSize;};
+   
+   // File manipulators
+   bool Sync();
+   bool Sync(unsigned long Start,unsigned long Stop);
+   
+   MMap(FileFd &F,unsigned long Flags);
+   MMap(unsigned long Flags);
+   virtual ~MMap();
+};
+
+class DynamicMMap : public MMap
+{
+   public:
+   
+   // This is the allocation pool structure
+   struct Pool
+   {
+      unsigned long ItemSize;
+      unsigned long Start;
+      unsigned long Count;
+   };
+   
+   protected:
+   
+   FileFd *Fd;
+   unsigned long WorkSpace;
+   Pool *Pools;
+   unsigned int PoolCount;
+   
+   public:
+
+   // Allocation
+   unsigned long RawAllocate(unsigned long Size,unsigned long Aln = 0);
+   unsigned long Allocate(unsigned long ItemSize);
+   unsigned long WriteString(const char *String,unsigned long Len = (unsigned long)-1);
+   inline unsigned long WriteString(string S) {return WriteString(S.c_str());};
+   void UsePools(Pool &P,unsigned int Count) {Pools = &P; PoolCount = Count;};
+   
+   DynamicMMap(FileFd &F,unsigned long Flags,unsigned long WorkSpace = 2*1024*1024);
+   DynamicMMap(unsigned long Flags,unsigned long WorkSpace = 2*1024*1024);
+   virtual ~DynamicMMap();
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/slidingwindow.cc b/tools/dsync-0.0/libdsync/contrib/slidingwindow.cc
new file mode 100644 (file)
index 0000000..ad6cdd2
--- /dev/null
@@ -0,0 +1,110 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: slidingwindow.cc,v 1.1 1999/11/05 05:47:06 jgg Exp $
+/* ######################################################################
+
+   Sliding Window - Implements a sliding buffer over a file. 
+
+   It would be possible to implement an alternate version if 
+   _POSIX_MAPPED_FILES is not defined.. 
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/slidingwindow.h"
+#endif
+
+#include <dsync/slidingwindow.h>
+#include <dsync/error.h>
+
+#include <sys/mman.h>
+#include <unistd.h>
+                                                                       /*}}}*/
+
+// SlidingWindow::SlidingWindow - Constructor                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+SlidingWindow::SlidingWindow(FileFd &Fd,unsigned long MnSize) : Buffer(0),
+                              MinSize(MnSize), Fd(Fd)
+{       
+   Offset = 0;
+   Left = 0;
+   PageSize = sysconf(_SC_PAGESIZE);
+      
+   if (MinSize < 1024*1024)
+      MinSize = 1024*1024;
+   MinSize = Align(MinSize);   
+}
+                                                                       /*}}}*/
+// SlidingWindow::~SlidingWindow - Destructor                          /*{{{*/
+// ---------------------------------------------------------------------
+/* Just unmap the mapping */
+SlidingWindow::~SlidingWindow()
+{
+   if (Buffer != 0)
+   {
+      if (munmap((char *)Buffer,Size) != 0)
+        _error->Warning("Unable to munmap");
+   }   
+}
+                                                                       /*}}}*/
+// SlidingWindow::Extend - Make Start - End longer                                                                     /*{{{*/
+// ---------------------------------------------------------------------
+/* Start == End when the file is exhausted, false is an IO error. */
+bool SlidingWindow::Extend(unsigned char *&Start,unsigned char *&End)
+{
+   unsigned long Remainder = 0;
+   
+   // Restart
+   if (Start == 0 || Buffer == 0)
+   {
+      Offset = 0;
+      Left = Fd.Size();
+   }
+   else
+   {
+      if (AlignDn((unsigned long)(Start - Buffer)) == 0)
+        return _error->Error("SlidingWindow::Extend called with too small a 'Start'");
+
+      // Scanning is finished.
+      if (Left < (off_t)Size)
+      {
+        End = Start;
+        return true;
+      }
+      
+      Offset += AlignDn((unsigned long)(Start - Buffer));
+      Left -= AlignDn((unsigned long)(Start - Buffer));
+      Remainder = (Start - Buffer) % PageSize;      
+   }
+
+   // Release the old region
+   if (Buffer != 0)
+   {
+      if (munmap((char *)Buffer,Size) != 0)
+        return _error->Errno("munmap","Unable to munmap");
+      Buffer = 0;
+   }
+
+   // Maximize the amount that can be mapped
+   if (Left < (off_t)MinSize)
+      Size = Align(Left);
+   else
+      Size = MinSize;
+   
+   // Map it
+   Buffer = (unsigned char *)mmap(0,Size,PROT_READ,MAP_PRIVATE,Fd.Fd(),Offset);
+   if (Buffer == (unsigned char *)-1)
+      return _error->Errno("mmap","Couldn't make mmap %lu->%lu bytes",(unsigned long)Offset,
+                          Size);
+   
+   // Reposition
+   if (Left < (off_t)Size)
+      End = Buffer + Left;
+   else
+      End = Buffer + Size;
+   Start = Buffer + Remainder;
+   return true;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/slidingwindow.h b/tools/dsync-0.0/libdsync/contrib/slidingwindow.h
new file mode 100644 (file)
index 0000000..40a7875
--- /dev/null
@@ -0,0 +1,57 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: slidingwindow.h,v 1.2 1999/11/15 07:59:49 jgg Exp $
+/* ######################################################################
+   
+   Sliding Window - Implements a sliding buffer over a file. 
+   
+   The buffer can be of arbitary size and where possible mmap is used
+   to optimize IO.
+   
+   To use, init the class and then call Extend with a 0 input pointer
+   to receive the first block and then call extend with Start <= End 
+   to get the next block. If Start != End then Start will be returned
+   with a new value, but pointing at the same byte, that is the new
+   region will contain the subregion Start -> End(o) but with a new
+   length End-Start, End != End(o).
+   
+   After the file has been exhausted Start == End will be returned, but
+   the old region Start -> End(o) will remain valid.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef SLIDING_WINDOW_H
+#define SLIDING_WINDOW_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/slidingwindow.h"
+#endif 
+
+#include <sys/types.h>
+#include <dsync/fileutl.h>
+
+class SlidingWindow
+{
+   unsigned char *Buffer;
+   unsigned long Size;
+   unsigned long MinSize;
+   FileFd &Fd;
+   unsigned long PageSize;
+   off_t Offset;
+   off_t Left;
+   
+   inline unsigned long Align(off_t V) const {return ((V % PageSize) == 0)?V:V + PageSize - (V % PageSize);};
+   inline unsigned long Align(unsigned long V) const {return ((V % PageSize) == 0)?V:V + PageSize - (V % PageSize);};
+   inline unsigned long AlignDn(off_t V) const {return ((V % PageSize) == 0)?V:V - (V % PageSize);};
+   inline unsigned long AlignDn(unsigned long V) const {return ((V % PageSize) == 0)?V:V - (V % PageSize);};
+   
+   public:
+
+   // Make the distance Start - End longer if possible
+   bool Extend(unsigned char *&Start,unsigned char *&End);
+   
+   SlidingWindow(FileFd &Fd,unsigned long MinSize = 0);
+   ~SlidingWindow();
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/strutl.cc b/tools/dsync-0.0/libdsync/contrib/strutl.cc
new file mode 100644 (file)
index 0000000..5b49c31
--- /dev/null
@@ -0,0 +1,853 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: strutl.cc,v 1.4 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+
+   String Util - Some usefull string functions.
+
+   These have been collected from here and there to do all sorts of usefull
+   things to strings. They are usefull in file parsers, URI handlers and
+   especially in APT methods.   
+   
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Jason Gunthorpe <jgg@gpu.srv.ualberta.ca>
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Includes                                                            /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/strutl.h"
+#endif
+
+#include <dsync/strutl.h>
+#include <dsync/fileutl.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+                                                                       /*}}}*/
+
+// strstrip - Remove white space from the front and back of a string   /*{{{*/
+// ---------------------------------------------------------------------
+/* This is handy to use when parsing a file. It also removes \n's left 
+   over from fgets and company */
+char *_strstrip(char *String)
+{
+   for (;*String != 0 && (*String == ' ' || *String == '\t'); String++);
+
+   if (*String == 0)
+      return String;
+
+   char *End = String + strlen(String) - 1;
+   for (;End != String - 1 && (*End == ' ' || *End == '\t' || *End == '\n' ||
+                              *End == '\r'); End--);
+   End++;
+   *End = 0;
+   return String;
+};
+                                                                       /*}}}*/
+// strtabexpand - Converts tabs into 8 spaces                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+char *_strtabexpand(char *String,size_t Len)
+{
+   for (char *I = String; I != I + Len && *I != 0; I++)
+   {
+      if (*I != '\t')
+        continue;
+      if (I + 8 > String + Len)
+      {
+        *I = 0;
+        return String;
+      }
+
+      /* Assume the start of the string is 0 and find the next 8 char
+         division */
+      int Len;
+      if (String == I)
+        Len = 1;
+      else
+        Len = 8 - ((String - I) % 8);
+      Len -= 2;
+      if (Len <= 0)
+      {
+        *I = ' ';
+        continue;
+      }
+      
+      memmove(I + Len,I + 1,strlen(I) + 1);
+      for (char *J = I; J + Len != I; *I = ' ', I++);
+   }
+   return String;
+}
+                                                                       /*}}}*/
+// ParseQuoteWord - Parse a single word out of a string                        /*{{{*/
+// ---------------------------------------------------------------------
+/* This grabs a single word, converts any % escaped characters to their
+   proper values and advances the pointer. Double quotes are understood
+   and striped out as well. This is for URI/URL parsing. */
+bool ParseQuoteWord(const char *&String,string &Res)
+{
+   // Skip leading whitespace
+   const char *C = String;
+   for (;*C != 0 && *C == ' '; C++);
+   if (*C == 0)
+      return false;
+   
+   // Jump to the next word
+   for (;*C != 0 && isspace(*C) == 0; C++)
+   {
+      if (*C == '"')
+      {
+        for (C++;*C != 0 && *C != '"'; C++);
+        if (*C == 0)
+           return false;
+      }
+   }
+
+   // Now de-quote characters
+   char Buffer[1024];
+   char Tmp[3];
+   const char *Start = String;
+   char *I;
+   for (I = Buffer; I < Buffer + sizeof(Buffer) && Start != C; I++)
+   {
+      if (*Start == '%' && Start + 2 < C)
+      {
+        Tmp[0] = Start[1];
+        Tmp[1] = Start[2];
+        Tmp[2] = 0;
+        *I = (char)strtol(Tmp,0,16);
+        Start += 3;
+        continue;
+      }
+      if (*Start != '"')
+        *I = *Start;
+      else
+        I--;
+      Start++;
+   }
+   *I = 0;
+   Res = Buffer;
+   
+   // Skip ending white space
+   for (;*C != 0 && isspace(*C) != 0; C++);
+   String = C;
+   return true;
+}
+                                                                       /*}}}*/
+// ParseCWord - Parses a string like a C "" expression                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This expects a series of space seperated strings enclosed in ""'s. 
+   It concatenates the ""'s into a single string. */
+bool ParseCWord(const char *String,string &Res)
+{
+   // Skip leading whitespace
+   const char *C = String;
+   for (;*C != 0 && *C == ' '; C++);
+   if (*C == 0)
+      return false;
+   
+   char Buffer[1024];
+   char *Buf = Buffer;
+   if (strlen(String) >= sizeof(Buffer))
+       return false;
+       
+   for (; *C != 0; C++)
+   {
+      if (*C == '"')
+      {
+        for (C++; *C != 0 && *C != '"'; C++)
+           *Buf++ = *C;
+        
+        if (*C == 0)
+           return false;
+        
+        continue;
+      }      
+      
+      if (C != String && isspace(*C) != 0 && isspace(C[-1]) != 0)
+        continue;
+      if (isspace(*C) == 0)
+        return false;
+      *Buf++ = ' ';
+   }   
+   *Buf = 0;
+   Res = Buffer;
+   return true;
+}
+                                                                       /*}}}*/
+// QuoteString - Convert a string into quoted from                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+string QuoteString(string Str,const char *Bad)
+{
+   string Res;
+   for (string::iterator I = Str.begin(); I != Str.end(); I++)
+   {
+      if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || 
+         *I <= 0x20 || *I >= 0x7F)
+      {
+        char Buf[10];
+        sprintf(Buf,"%%%02x",(int)*I);
+        Res += Buf;
+      }
+      else
+        Res += *I;
+   }
+   return Res;
+}
+                                                                       /*}}}*/
+// DeQuoteString - Convert a string from quoted from                    /*{{{*/
+// ---------------------------------------------------------------------
+/* This undoes QuoteString */
+string DeQuoteString(string Str)
+{
+   string Res;
+   for (string::iterator I = Str.begin(); I != Str.end(); I++)
+   {
+      if (*I == '%' && I + 2 < Str.end())
+      {
+        char Tmp[3];
+        Tmp[0] = I[1];
+        Tmp[1] = I[2];
+        Tmp[2] = 0;
+        Res += (char)strtol(Tmp,0,16);
+        I += 2;
+        continue;
+      }
+      else
+        Res += *I;
+   }
+   return Res;   
+}
+
+                                                                        /*}}}*/
+// SizeToStr - Convert a long into a human readable size               /*{{{*/
+// ---------------------------------------------------------------------
+/* A max of 4 digits are shown before conversion to the next highest unit. 
+   The max length of the string will be 5 chars unless the size is > 10
+   YottaBytes (E24) */
+string SizeToStr(double Size)
+{
+   char S[300];
+   double ASize;
+   if (Size >= 0)
+      ASize = Size;
+   else
+      ASize = -1*Size;
+   
+   /* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes, 
+      ExaBytes, ZettaBytes, YottaBytes */
+   char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'};
+   int I = 0;
+   while (I <= 8)
+   {
+      if (ASize < 100 && I != 0)
+      {
+         sprintf(S,"%.1f%c",ASize,Ext[I]);
+        break;
+      }
+      
+      if (ASize < 10000)
+      {
+         sprintf(S,"%.0f%c",ASize,Ext[I]);
+        break;
+      }
+      ASize /= 1000.0;
+      I++;
+   }
+   
+   return S;
+}
+                                                                       /*}}}*/
+// TimeToStr - Convert the time into a string                          /*{{{*/
+// ---------------------------------------------------------------------
+/* Converts a number of seconds to a hms format */
+string TimeToStr(unsigned long Sec)
+{
+   char S[300];
+   
+   while (1)
+   {
+      if (Sec > 60*60*24)
+      {
+        sprintf(S,"%lid %lih%lim%lis",Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60);
+        break;
+      }
+      
+      if (Sec > 60*60)
+      {
+        sprintf(S,"%lih%lim%lis",Sec/60/60,(Sec/60) % 60,Sec % 60);
+        break;
+      }
+      
+      if (Sec > 60)
+      {
+        sprintf(S,"%lim%lis",Sec/60,Sec % 60);
+        break;
+      }
+      
+      sprintf(S,"%lis",Sec);
+      break;
+   }
+   
+   return S;
+}
+                                                                       /*}}}*/
+// SubstVar - Substitute a string for another string                   /*{{{*/
+// ---------------------------------------------------------------------
+/* This replaces all occurances of Subst with Contents in Str. */
+string SubstVar(string Str,string Subst,string Contents)
+{
+   string::size_type Pos = 0;
+   string::size_type OldPos = 0;
+   string Temp;
+   
+   while (OldPos < Str.length() && 
+         (Pos = Str.find(Subst,OldPos)) != string::npos)
+   {
+      Temp += string(Str,OldPos,Pos) + Contents;
+      OldPos = Pos + Subst.length();      
+   }
+   
+   if (OldPos == 0)
+      return Str;
+   
+   return Temp + string(Str,OldPos);
+}
+                                                                       /*}}}*/
+// URItoFileName - Convert the uri into a unique file name             /*{{{*/
+// ---------------------------------------------------------------------
+/* This converts a URI into a safe filename. It quotes all unsafe characters
+   and converts / to _ and removes the scheme identifier. The resulting
+   file name should be unique and never occur again for a different file */
+string URItoFileName(string URI)
+{
+   // Nuke 'sensitive' items
+   ::URI U(URI);
+   U.User = string();
+   U.Password = string();
+   U.Access = "";
+   
+   // "\x00-\x20{}|\\\\^\\[\\]<>\"\x7F-\xFF";
+   URI = QuoteString(U,"\\|{}[]<>\"^~_=!@#$%^&*");
+   string::iterator J = URI.begin();
+   for (; J != URI.end(); J++)
+      if (*J == '/') 
+        *J = '_';
+   return URI;
+}
+                                                                       /*}}}*/
+// Base64Encode - Base64 Encoding routine for short strings            /*{{{*/
+// ---------------------------------------------------------------------
+/* This routine performs a base64 transformation on a string. It was ripped
+   from wget and then patched and bug fixed.
+   This spec can be found in rfc2045 */
+string Base64Encode(string S)
+{
+   // Conversion table.
+   static char tbl[64] = {'A','B','C','D','E','F','G','H',
+                         'I','J','K','L','M','N','O','P',
+                          'Q','R','S','T','U','V','W','X',
+                          'Y','Z','a','b','c','d','e','f',
+                          'g','h','i','j','k','l','m','n',
+                          'o','p','q','r','s','t','u','v',
+                          'w','x','y','z','0','1','2','3',
+                          '4','5','6','7','8','9','+','/'};
+   
+   // Pre-allocate some space
+   string Final;
+   Final.reserve((4*S.length() + 2)/3 + 2);
+
+   /* Transform the 3x8 bits to 4x6 bits, as required by
+      base64.  */
+   for (string::const_iterator I = S.begin(); I < S.end(); I += 3)
+   {
+      char Bits[3] = {0,0,0};
+      Bits[0] = I[0];
+      if (I + 1 < S.end())
+        Bits[1] = I[1];
+      if (I + 2 < S.end())
+        Bits[2] = I[2];
+
+      Final += tbl[Bits[0] >> 2];
+      Final += tbl[((Bits[0] & 3) << 4) + (Bits[1] >> 4)];
+      
+      if (I + 1 >= S.end())
+        break;
+      
+      Final += tbl[((Bits[1] & 0xf) << 2) + (Bits[2] >> 6)];
+      
+      if (I + 2 >= S.end())
+        break;
+      
+      Final += tbl[Bits[2] & 0x3f];
+   }
+
+   /* Apply the padding elements, this tells how many bytes the remote
+      end should discard */
+   if (S.length() % 3 == 2)
+      Final += '=';
+   if (S.length() % 3 == 1)
+      Final += "==";
+   
+   return Final;
+}
+                                                                       /*}}}*/
+// stringcmp - Arbitary string compare                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This safely compares two non-null terminated strings of arbitary 
+   length */
+int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
+{
+   for (; A != AEnd && B != BEnd; A++, B++)
+      if (*A != *B)
+        break;
+   
+   if (A == AEnd && B == BEnd)
+      return 0;
+   if (A == AEnd)
+      return 1;
+   if (B == BEnd)
+      return -1;
+   if (*A < *B)
+      return -1;
+   return 1;
+}
+                                                                       /*}}}*/
+// stringcasecmp - Arbitary case insensitive string compare            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
+{
+   for (; A != AEnd && B != BEnd; A++, B++)
+      if (toupper(*A) != toupper(*B))
+        break;
+
+   if (A == AEnd && B == BEnd)
+      return 0;
+   if (A == AEnd)
+      return 1;
+   if (B == BEnd)
+      return -1;
+   if (toupper(*A) < toupper(*B))
+      return -1;
+   return 1;
+}
+                                                                       /*}}}*/
+// LookupTag - Lookup the value of a tag in a taged string             /*{{{*/
+// ---------------------------------------------------------------------
+/* The format is like those used in package files and the method 
+   communication system */
+string LookupTag(string Message,const char *Tag,const char *Default)
+{
+   // Look for a matching tag.
+   int Length = strlen(Tag);
+   for (string::iterator I = Message.begin(); I + Length < Message.end(); I++)
+   {
+      // Found the tag
+      const char *i = Message.c_str() + (I - Message.begin());
+      if (I[Length] == ':' && stringcasecmp(i,i+Length,Tag) == 0)
+      {
+        // Find the end of line and strip the leading/trailing spaces
+        string::iterator J;
+        I += Length + 1;
+        for (; isspace(*I) != 0 && I < Message.end(); I++);
+        for (J = I; *J != '\n' && J < Message.end(); J++);
+        for (; J > I && isspace(J[-1]) != 0; J--);
+        
+        return string(i,J-I);
+      }
+      
+      for (; *I != '\n' && I < Message.end(); I++);
+   }   
+   
+   // Failed to find a match
+   if (Default == 0)
+      return string();
+   return Default;
+}
+                                                                       /*}}}*/
+// StringToBool - Converts a string into a boolean                     /*{{{*/
+// ---------------------------------------------------------------------
+/* This inspects the string to see if it is true or if it is false and
+   then returns the result. Several varients on true/false are checked. */
+int StringToBool(string Text,int Default)
+{
+   char *End;
+   int Res = strtol(Text.c_str(),&End,0);   
+   if (End != Text.c_str() && Res >= 0 && Res <= 1)
+      return Res;
+   
+   // Check for positives
+   if (strcasecmp(Text.c_str(),"no") == 0 ||
+       strcasecmp(Text.c_str(),"false") == 0 ||
+       strcasecmp(Text.c_str(),"without") == 0 ||
+       strcasecmp(Text.c_str(),"off") == 0 ||
+       strcasecmp(Text.c_str(),"disable") == 0)
+      return 0;
+   
+   // Check for negatives
+   if (strcasecmp(Text.c_str(),"yes") == 0 ||
+       strcasecmp(Text.c_str(),"true") == 0 ||
+       strcasecmp(Text.c_str(),"with") == 0 ||
+       strcasecmp(Text.c_str(),"on") == 0 ||
+       strcasecmp(Text.c_str(),"enable") == 0)
+      return 1;
+   
+   return Default;
+}
+                                                                       /*}}}*/
+// TimeRFC1123 - Convert a time_t into RFC1123 format                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This converts a time_t into a string time representation that is
+   year 2000 complient and timezone neutral */
+string TimeRFC1123(time_t Date)
+{
+   struct tm Conv = *gmtime(&Date);
+   char Buf[300];
+
+   const char *Day[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
+   const char *Month[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul",
+                          "Aug","Sep","Oct","Nov","Dec"};
+
+   sprintf(Buf,"%s, %02i %s %i %02i:%02i:%02i GMT",Day[Conv.tm_wday],
+          Conv.tm_mday,Month[Conv.tm_mon],Conv.tm_year+1900,Conv.tm_hour,
+          Conv.tm_min,Conv.tm_sec);
+   return Buf;
+}
+                                                                       /*}}}*/
+// ReadMessages - Read messages from the FD                            /*{{{*/
+// ---------------------------------------------------------------------
+/* This pulls full messages from the input FD into the message buffer. 
+   It assumes that messages will not pause during transit so no
+   fancy buffering is used. */
+bool ReadMessages(int Fd, vector<string> &List)
+{
+   char Buffer[4000];
+   char *End = Buffer;
+   
+   while (1)
+   {
+      int Res = read(Fd,End,sizeof(Buffer) - (End-Buffer));
+      if (Res < 0 && errno == EINTR)
+        continue;
+      
+      // Process is dead, this is kind of bad..
+      if (Res == 0)
+        return false;
+      
+      // No data
+      if (Res <= 0)
+        return true;
+
+      End += Res;
+      
+      // Look for the end of the message
+      for (char *I = Buffer; I + 1 < End; I++)
+      {
+        if (I[0] != '\n' || I[1] != '\n')
+           continue;
+        
+        // Pull the message out
+        string Message(Buffer,0,I-Buffer);
+
+        // Fix up the buffer
+        for (; I < End && *I == '\n'; I++);
+        End -= I-Buffer;        
+        memmove(Buffer,I,End-Buffer);
+        I = Buffer;
+        
+        List.push_back(Message);
+      }
+      if (End == Buffer)
+        return true;
+
+      if (WaitFd(Fd) == false)
+        return false;
+   }   
+}
+                                                                       /*}}}*/
+// MonthConv - Converts a month string into a number                   /*{{{*/
+// ---------------------------------------------------------------------
+/* This was lifted from the boa webserver which lifted it from 'wn-v1.07'
+   Made it a bit more robust with a few touppers though. */
+static int MonthConv(char *Month)
+{
+   switch (toupper(*Month)) 
+   {
+      case 'A':
+      return toupper(Month[1]) == 'P'?3:7;
+      case 'D':
+      return 11;
+      case 'F':
+      return 1;
+      case 'J':
+      if (toupper(Month[1]) == 'A')
+        return 0;
+      return toupper(Month[2]) == 'N'?5:6;
+      case 'M':
+      return toupper(Month[2]) == 'R'?2:4;
+      case 'N':
+      return 10;
+      case 'O':
+      return 9;
+      case 'S':
+      return 8;
+
+      // Pretend it is January..
+      default:
+      return 0;
+   }   
+}
+                                                                       /*}}}*/
+// timegm - Internal timegm function if gnu is not available           /*{{{*/
+// ---------------------------------------------------------------------
+/* Ripped this evil little function from wget - I prefer the use of 
+   GNU timegm if possible as this technique will have interesting problems
+   with leap seconds, timezones and other.
+   
+   Converts struct tm to time_t, assuming the data in tm is UTC rather
+   than local timezone (mktime assumes the latter).
+   
+   Contributed by Roger Beeman <beeman@cisco.com>, with the help of
+   Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO. */
+#ifndef __USE_MISC        // glib sets this
+static time_t timegm(struct tm *t)
+{
+   time_t tl, tb;
+   
+   tl = mktime (t);
+   if (tl == -1)
+      return -1;
+   tb = mktime (gmtime (&tl));
+   return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
+}
+#endif
+                                                                       /*}}}*/
+// StrToTime - Converts a string into a time_t                         /*{{{*/
+// ---------------------------------------------------------------------
+/* This handles all 3 populare time formats including RFC 1123, RFC 1036
+   and the C library asctime format. It requires the GNU library function
+   'timegm' to convert a struct tm in UTC to a time_t. For some bizzar
+   reason the C library does not provide any such function :<*/
+bool StrToTime(string Val,time_t &Result)
+{
+   struct tm Tm;
+   char Month[10];
+   const char *I = Val.c_str();
+   
+   // Skip the day of the week
+   for (;*I != 0  && *I != ' '; I++);
+   
+   // Handle RFC 1123 time
+   if (sscanf(I," %d %3s %d %d:%d:%d GMT",&Tm.tm_mday,Month,&Tm.tm_year,
+             &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6)
+   {
+      // Handle RFC 1036 time
+      if (sscanf(I," %d-%3s-%d %d:%d:%d GMT",&Tm.tm_mday,Month,
+                &Tm.tm_year,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) == 6)
+        Tm.tm_year += 1900;
+      else
+      {
+        // asctime format
+        if (sscanf(I," %3s %d %d:%d:%d %d",Month,&Tm.tm_mday,
+                   &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec,&Tm.tm_year) != 6)
+           return false;
+      }
+   }
+   
+   Tm.tm_isdst = 0;
+   Tm.tm_mon = MonthConv(Month);
+   Tm.tm_year -= 1900;
+   
+   // Convert to local time and then to GMT
+   Result = timegm(&Tm);
+   return true;
+}
+                                                                       /*}}}*/
+// StrToNum - Convert a fixed length string to a number                        /*{{{*/
+// ---------------------------------------------------------------------
+/* This is used in decoding the crazy fixed length string headers in 
+   tar and ar files. */
+bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base)
+{
+   char S[30];
+   if (Len >= sizeof(S))
+      return false;
+   memcpy(S,Str,Len);
+   S[Len] = 0;
+   
+   // All spaces is a zero
+   Res = 0;
+   unsigned I;
+   for (I = 0; S[I] == ' '; I++);
+   if (S[I] == 0)
+      return true;
+   
+   char *End;
+   Res = strtoul(S,&End,Base);
+   if (End == S)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// HexDigit - Convert a hex character into an integer                  /*{{{*/
+// ---------------------------------------------------------------------
+/* Helper for Hex2Num */
+static int HexDigit(int c)
+{   
+   if (c >= '0' && c <= '9')
+      return c - '0';
+   if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+   if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+   return 0;
+}
+                                                                       /*}}}*/
+// Hex2Num - Convert a long hex number into a buffer                   /*{{{*/
+// ---------------------------------------------------------------------
+/* The length of the buffer must be exactly 1/2 the length of the string. */
+bool Hex2Num(const char *Start,const char *End,unsigned char *Num,
+            unsigned int Length)
+{
+   if (End - Start != (signed)(Length*2))
+      return false;
+   
+   // Convert each digit. We store it in the same order as the string
+   int J = 0;
+   for (const char *I = Start; I < End;J++, I += 2)
+   {
+      if (isxdigit(*I) == 0 || isxdigit(I[1]) == 0)
+        return false;
+      
+      Num[J] = HexDigit(I[0]) << 4;
+      Num[J] += HexDigit(I[1]);
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+// URI::CopyFrom - Copy from an object                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This parses the URI into all of its components */
+void URI::CopyFrom(string U)
+{
+   string::const_iterator I = U.begin();
+
+   // Locate the first colon, this seperates the scheme
+   for (; I < U.end() && *I != ':' ; I++);
+   string::const_iterator FirstColon = I;
+
+   /* Determine if this is a host type URI with a leading double //
+      and then search for the first single / */
+   string::const_iterator SingleSlash = I;
+   if (I + 3 < U.end() && I[1] == '/' && I[2] == '/')
+      SingleSlash += 3;
+   for (; SingleSlash < U.end() && *SingleSlash != '/'; SingleSlash++);
+   if (SingleSlash > U.end())
+      SingleSlash = U.end();
+
+   // We can now write the access and path specifiers
+   Access = string(U,0,FirstColon - U.begin());
+   if (SingleSlash != U.end())
+      Path = string(U,SingleSlash - U.begin());
+   if (Path.empty() == true)
+      Path = "/";
+
+   // Now we attempt to locate a user:pass@host fragment
+   if (FirstColon[1] == '/' && FirstColon[2] == '/')
+      FirstColon += 3;
+   else
+      FirstColon += 1;
+   if (FirstColon >= U.end())
+      return;
+   
+   if (FirstColon > SingleSlash)
+      FirstColon = SingleSlash;
+   
+   // Find the colon...
+   I = FirstColon + 1;
+   if (I > SingleSlash)
+      I = SingleSlash;
+   for (; I < SingleSlash && *I != ':'; I++);
+   string::const_iterator SecondColon = I;
+   
+   // Search for the @ after the colon
+   for (; I < SingleSlash && *I != '@'; I++);
+   string::const_iterator At = I;
+   
+   // Now write the host and user/pass
+   if (At == SingleSlash)
+   {
+      if (FirstColon < SingleSlash)
+        Host = string(U,FirstColon - U.begin(),SingleSlash - FirstColon);
+   }
+   else
+   {
+      Host = string(U,At - U.begin() + 1,SingleSlash - At - 1);
+      User = string(U,FirstColon - U.begin(),SecondColon - FirstColon);
+      if (SecondColon < At)
+        Password = string(U,SecondColon - U.begin() + 1,At - SecondColon - 1);
+   }   
+   
+   // Now we parse off a port number from the hostname
+   Port = 0;
+   string::size_type Pos = Host.rfind(':');
+   if (Pos == string::npos)
+      return;
+   
+   Port = atoi(string(Host,Pos+1).c_str());
+   Host = string(Host,0,Pos);
+}
+                                                                       /*}}}*/
+// URI::operator string - Convert the URI to a string                  /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+URI::operator string()
+{
+   string Res;
+   
+   if (Access.empty() == false)
+      Res = Access + ':';
+   
+   if (Host.empty() == false)
+   {
+      if (Access.empty() == false)
+        Res += "//";
+      
+      if (User.empty() == false)
+      {
+        Res +=  User;
+        if (Password.empty() == false)
+           Res += ":" + Password;
+        Res += "@";
+      }
+      
+      Res += Host;
+      if (Port != 0)
+      {
+        char S[30];
+        sprintf(S,":%u",Port);
+        Res += S;
+      }         
+   }
+   
+   if (Path.empty() == false)
+   {
+      if (Path[0] != '/')
+        Res += "/" + Path;
+      else
+        Res += Path;
+   }
+   
+   return Res;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/contrib/strutl.h b/tools/dsync-0.0/libdsync/contrib/strutl.h
new file mode 100644 (file)
index 0000000..e1e5ada
--- /dev/null
@@ -0,0 +1,78 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: strutl.h,v 1.2 1999/10/24 06:53:12 jgg Exp $
+/* ######################################################################
+
+   String Util - These are some usefull string functions
+   
+   _strstrip is a function to remove whitespace from the front and end
+   of a string.
+   
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Jason Gunthorpe <jgg@gpu.srv.ualberta.ca>   
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef STRUTL_H
+#define STRUTL_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/strutl.h"
+#endif 
+
+#include <stdlib.h>
+#include <string>
+#include <vector>
+#include <time.h>
+
+using namespace std;
+
+char *_strstrip(char *String);
+char *_strtabexpand(char *String,size_t Len);
+bool ParseQuoteWord(const char *&String,string &Res);
+bool ParseCWord(const char *String,string &Res);
+string QuoteString(string Str,const char *Bad);
+string DeQuoteString(string Str);
+string SizeToStr(double Bytes);
+string TimeToStr(unsigned long Sec);
+string SubstVar(string Str,string Subst,string Contents);
+string Base64Encode(string Str);
+string URItoFileName(string URI);
+string TimeRFC1123(time_t Date);
+bool StrToTime(string Val,time_t &Result);
+string LookupTag(string Message,const char *Tag,const char *Default = 0);
+int StringToBool(string Text,int Default = -1);
+bool ReadMessages(int Fd, vector<string> &List);
+bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base = 0);
+bool Hex2Num(const char *Start,const char *End,unsigned char *Num,
+            unsigned int Length);
+
+int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd);
+inline int stringcmp(const char *A,const char *AEnd,const char *B) {return stringcmp(A,AEnd,B,B+strlen(B));};
+inline int stringcmp(string A,const char *B) {return stringcmp(A.c_str(),A.c_str()+strlen(A.c_str()),B,B+strlen(B));};
+int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd);
+inline int stringcasecmp(const char *A,const char *AEnd,const char *B) {return stringcasecmp(A,AEnd,B,B+strlen(B));};
+inline int stringcasecmp(string A,const char *B) {return stringcasecmp(A.c_str(),A.c_str()+strlen(A.c_str()),B,B+strlen(B));};
+
+class URI
+{
+   void CopyFrom(string From);
+                
+   public:
+   
+   string Access;
+   string User;
+   string Password;
+   string Host;
+   string Path;
+   unsigned int Port;
+   
+   operator string();
+   inline void operator =(string From) {CopyFrom(From);};
+   inline bool empty() {return Access.empty();};
+   
+   URI(string Path) {CopyFrom(Path);};
+   URI() : Port(0) {};
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/contrib/system.h b/tools/dsync-0.0/libdsync/contrib/system.h
new file mode 100644 (file)
index 0000000..13434fe
--- /dev/null
@@ -0,0 +1,56 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: system.h,v 1.2 1999/01/19 04:41:43 jgg Exp $
+/* ######################################################################
+   
+   System Header - Usefull private definitions
+
+   This source is placed in the Public Domain, do with it what you will
+   It was originally written by Brian C. White.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Private header
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+// MIN_VAL(SINT16) will return -0x8000 and MAX_VAL(SINT16) = 0x7FFF
+#define        MIN_VAL(t)      (((t)(-1) > 0) ? (t)( 0) : (t)(((1L<<(sizeof(t)*8-1))  )))
+#define        MAX_VAL(t)      (((t)(-1) > 0) ? (t)(-1) : (t)(((1L<<(sizeof(t)*8-1))-1)))
+
+// Min/Max functions
+#if defined(__HIGHC__)
+#define MIN(x,y) _min(x,y)
+#define MAX(x,y) _max(x,y)
+#endif
+
+// GNU C++ has a min/max operator <coolio>
+#if defined(__GNUG__)
+#define MIN(A,B) ((A) <? (B))
+#define MAX(A,B) ((A) >? (B))
+#endif
+
+/* Templates tend to mess up existing code that uses min/max because of the
+   strict matching requirements */
+#if !defined(MIN)
+#define MIN(A,B) ((A) < (B)?(A):(B))
+#define MAX(A,B) ((A) > (B)?(A):(B))
+#endif
+
+/* Bound functions, bound will return the value b within the limits a-c
+   bounv will change b so that it is within the limits of a-c. */
+#define _bound(a,b,c) MIN(c,MAX(b,a))
+#define _boundv(a,b,c) b = _bound(a,b,c)
+#define ABS(a) (((a) < (0)) ?-(a) : (a))
+
+/* Usefull count macro, use on an array of things and it will return the
+   number of items in the array */
+#define _count(a) (sizeof(a)/sizeof(a[0]))
+
+// Flag Macros
+#define        FLAG(f)                 (1L << (f))
+#define        SETFLAG(v,f)    ((v) |= FLAG(f))
+#define CLRFLAG(v,f)   ((v) &=~FLAG(f))
+#define        CHKFLAG(v,f)    ((v) &  FLAG(f) ? true : false)
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/filefilter.cc b/tools/dsync-0.0/libdsync/filefilter.cc
new file mode 100644 (file)
index 0000000..0eca9b4
--- /dev/null
@@ -0,0 +1,150 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: filefilter.cc,v 1.4 1999/08/05 03:22:55 jgg Exp $
+/* ######################################################################
+
+   File Filter - Regular Expression maching filter
+   
+   The idea for this was stolen shamelessly from rsync.
+
+   Doesn't work:
+    dsync-flist -e binary-alpha -i binary-all -i binary-i386 generate /tmp/listing
+   
+   And various other incantations like that.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/filefilter.h"
+#endif
+
+#include <dsync/filefilter.h>
+#include <dsync/error.h>
+
+#include <fnmatch.h>
+using namespace std;
+                                                                       /*}}}*/
+
+// FileFilter::dsFileFilter - Constructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsFileFilter::dsFileFilter() : List(0)
+{
+}
+                                                                       /*}}}*/
+// FileFilter::~dsFileFilter - Destructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsFileFilter::~dsFileFilter()
+{
+   while (List != 0)
+   {
+      Item *Tmp = List;
+      List = Tmp->Next;
+      delete Tmp;
+   }
+}
+                                                                       /*}}}*/
+// FileFilter::Test - Test a directory and file                                /*{{{*/
+// ---------------------------------------------------------------------
+/* This will return true if the named entity is included by the filter, false
+   otherwise. By default all entries are included. */
+bool dsFileFilter::Test(const char *Directory,const char *File)
+{
+   for (Item *I = List; I != 0; I = I->Next)
+   {
+      bool Res = I->Test(Directory,File);
+      if (Res == false)
+        continue;
+      
+      if (I->Type == Item::Include)
+        return true;
+      
+      if (I->Type == Item::Exclude)
+        return false;
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// FileFilter::LoadFilter - Load the filter list from the configuration        /*{{{*/
+// ---------------------------------------------------------------------
+/* When given the root of a configuration tree this will parse that sub-tree
+   as an ordered list of include/exclude directives. Each value in the list
+   must be prefixed with a + or a - indicating include/exclude */
+bool dsFileFilter::LoadFilter(Configuration::Item const *Top)
+{
+   if (Top != 0)
+      Top = Top->Child;
+   
+   // Advance to the end of the list
+   Item **End = &List;
+   for (; *End != 0; End = &(*End)->Next);
+      
+   for (; Top != 0;)
+   {
+      Item *New = new Item;
+      
+      // Decode the type
+      if (Top->Value[0] == '+')
+        New->Type = Item::Include;
+      else
+      {
+        if (Top->Value[0] == '-')
+           New->Type = Item::Exclude;
+         else
+         {
+           delete New;
+           return _error->Error("Malformed filter directive %s",Top->Tag.c_str());
+        }
+      }
+
+      // Strip off the +/- indicator
+      unsigned int Count = 1;
+      for (const char *I = Top->Value.c_str() + 1; I < Top->Value.c_str() + strlen(Top->Value.c_str()) &&
+            isspace(*I); I++)
+        Count++;
+      New->Pattern = string(Top->Value,Count);
+      
+      // Set flags
+      New->Flags = 0;
+      if (New->Pattern == "*")
+        New->Flags |= Item::MatchAll;
+      if (New->Pattern.find('/') != string::npos)
+        New->Flags |= Item::MatchPath;
+      
+      // Link it into the list
+      New->Next = 0;
+      *End = New;
+      End = &New->Next;
+      
+      Top = Top->Next;
+   }
+   return true;
+}
+                                                                       /*}}}*/
+// FileFilter::Item::Test - Test a single item                         /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFileFilter::Item::Test(const char *Directory,const char *File)
+{
+   // Catch all
+   if ((Flags & MatchAll) == MatchAll)
+      return true;
+   // Append the direcotry
+   if ((Flags & MatchPath) == MatchPath)
+   {
+      char S[1024];
+      if (strlen(Directory) + strlen(File) > sizeof(S))
+         return _error->Error("File field overflow");
+      strcpy(S,Directory);
+      strcat(S,File);
+      
+      return fnmatch(Pattern.c_str(),S,FNM_PATHNAME) == 0;
+   }
+   
+   return fnmatch(Pattern.c_str(),File,FNM_PATHNAME) == 0;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/filefilter.h b/tools/dsync-0.0/libdsync/filefilter.h
new file mode 100644 (file)
index 0000000..ff13143
--- /dev/null
@@ -0,0 +1,60 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: filefilter.h,v 1.2 1998/12/30 05:36:41 jgg Exp $
+/* ######################################################################
+   
+   File Filter - Regular Expression maching filter
+   
+   This implements an ordered include/exclude filter list that can be used
+   to filter filenames.
+
+   Pattern matching is done identically to rsync, the key points are:
+    - Patterns containing / are matched against the whole path, otherwise
+      only the file name is used.
+    - Patterns that end in a / only match directories
+    - Wildcards supported by fnmatch (?*[)
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_FILEFILTER
+#define DSYNC_FILEFILTER
+
+#ifdef __GNUG__
+#pragma interface "dsync/filefilter.h"
+#endif 
+
+#include <string>
+#include <dsync/configuration.h>
+
+class dsFileFilter
+{
+   protected:
+   
+   struct Item
+   {
+      enum {Include, Exclude} Type;
+      string Pattern;
+      
+      // Various flags.
+      enum {MatchAll = (1<<0), MatchPath = (1<<1)};
+      unsigned long Flags;
+      
+      Item *Next;
+
+      bool Test(const char *Directory,const char *File);
+   };
+   Item *List;
+   
+   public:
+
+   // Members to see if the filter hits or misses
+   bool Test(const char *Directory,const char *File);
+   
+   // Load the filter from a configuration space
+   bool LoadFilter(Configuration::Item const *Root);
+      
+   dsFileFilter();
+   ~dsFileFilter();
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/filelist.cc b/tools/dsync-0.0/libdsync/filelist.cc
new file mode 100644 (file)
index 0000000..7711c13
--- /dev/null
@@ -0,0 +1,867 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: filelist.cc,v 1.14 1999/12/26 06:59:00 jgg Exp $
+/* ######################################################################
+   
+   File List Structures
+
+   This module has a large group of services all relating to the binary
+   file list. Each individual record type has an read and write function
+   that can be used to store it into a unpacked structure.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/filelist.h"
+#endif
+
+#include <dsync/filelist.h>
+#include <dsync/error.h>
+#include <system.h>
+
+#include <time.h>
+#include <stdio.h>
+#include <iostream>
+using namespace std;
+                                                                       /*}}}*/
+
+// FList::Step - Step to the next record                               /*{{{*/
+// ---------------------------------------------------------------------
+/* This is an encompassing function to read a single record of any type
+   from the IO */
+bool dsFList::Step(IO &IO)
+{
+   if (!(_error->PendingError() == false && IO.ReadInt(Tag,1) == true))
+      return false;
+
+   Entity = 0;
+   File = 0;
+   
+   switch (Tag)
+   {
+      case dsFList::tHeader:
+      Head.Tag = Tag;
+      Head.Read(IO);
+      IO.Header = Head;
+      break;
+        
+      case dsFList::tDirMarker:
+      case dsFList::tDirStart:
+      case dsFList::tDirectory:
+      Dir.Tag = Tag;
+      Entity = &Dir;
+      return Dir.Read(IO);
+            
+      case dsFList::tNormalFile:
+      NFile.Tag = Tag;
+      Entity = &NFile;
+      File = &NFile;
+      return NFile.Read(IO);
+        
+      case dsFList::tSymlink:
+      SLink.Tag = Tag;
+      Entity = &SLink;
+      return SLink.Read(IO);
+        
+      case dsFList::tDeviceSpecial:
+      DevSpecial.Tag = Tag;
+      Entity = &DevSpecial;
+      return DevSpecial.Read(IO);
+      
+      case dsFList::tFilter:
+      Filt.Tag = Tag;
+      return Filt.Read(IO);
+      
+      case dsFList::tUidMap:
+      UMap.Tag = Tag;
+      return UMap.Read(IO);
+      
+      case dsFList::tGidMap:
+      UMap.Tag = Tag;
+      return UMap.Read(IO);
+      
+      case dsFList::tHardLink:
+      HLink.Tag = Tag;
+      Entity = &HLink;
+      File = &HLink;
+      return HLink.Read(IO);
+      
+      case dsFList::tTrailer:
+      Trail.Tag = Tag;
+      return Trail.Read(IO);
+
+      case dsFList::tRSyncChecksum:
+      RChk.Tag = Tag;
+      return RChk.Read(IO);
+      
+      case dsFList::tAggregateFile:
+      AgFile.Tag = Tag;
+      return AgFile.Read(IO);
+        
+      case tRSyncEnd:
+      case tDirEnd:
+      return true;
+      
+      default:
+      return _error->Error("Corrupted file list");
+   }
+   return true;
+}
+                                                                       /*}}}*/
+// FList::Print - Print out the record                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This simply displays the record */
+bool dsFList::Print(ostream &out)
+{
+   char S[1024];
+   switch (Tag)
+   {
+      case tHeader:
+      {
+        snprintf(S,sizeof(S),"H Sig=%lx Maj=%lu Min=%lu Epoch=%lu Count=%lu\n",
+                 Head.Signature,Head.MajorVersion,Head.MinorVersion,
+                 Head.Epoch,Head.FlagCount);
+        out << S;
+        break;
+      }
+        
+      case tDirMarker:
+      case tDirStart:
+      case tDirectory:
+      {
+        if (Tag == tDirMarker)
+           snprintf(S,sizeof(S),"DM Mod=%lu",
+                    Dir.ModTime+Head.Epoch);
+        if (Tag == tDirStart)
+           snprintf(S,sizeof(S),"DS Mod=%lu",
+                    Dir.ModTime+Head.Epoch);
+        if (Tag == tDirectory)
+           snprintf(S,sizeof(S),"D Mod=%lu",
+                    Dir.ModTime+Head.Epoch);
+        out << S;
+        if ((Head.Flags[Tag] & Directory::FlPerm) != 0)
+        {
+           snprintf(S,sizeof(S)," Perm=%lo",Dir.Permissions);
+           out << S;
+        }
+        
+        if ((Head.Flags[Tag] & Directory::FlOwner) != 0)
+        {
+           snprintf(S,sizeof(S)," U=%lu G=%lu",Dir.User,Dir.Group);
+           out << S;
+        }
+        
+        snprintf(S,sizeof(S)," N='%s'\n",Dir.Name.c_str());
+        out << S;
+        break;
+      }
+      
+      case tDirEnd:
+      out << "DE" << endl;
+      break;
+
+      case tHardLink:
+      case tNormalFile:
+      {
+        snprintf(S,sizeof(S),"F Mod=%lu",File->ModTime+Head.Epoch);
+        out << S;
+        if ((Head.Flags[Tag] & NormalFile::FlPerm) != 0)
+        {
+           snprintf(S,sizeof(S)," Perm=%lo",File->Permissions);
+           out << S;
+        }
+        if ((Head.Flags[Tag] & NormalFile::FlOwner) != 0)
+        {
+           snprintf(S,sizeof(S)," U=%lu G=%lu",File->User,File->Group);
+           out << S;
+        }       
+        if ((Head.Flags[Tag] & NormalFile::FlMD5) != 0)
+        {
+           char S[16*2+1];
+           for (unsigned int I = 0; I != 16; I++)
+              sprintf(S+2*I,"%02x",File->MD5[I]);
+           S[16*2] = 0;
+           out << " MD5=" << S;
+        }
+        
+        if (Tag == tHardLink)
+           out << " Ser=" << HLink.Serial;
+        snprintf(S,sizeof(S)," Sz=%lu N='%s'\n",File->Size,File->Name.c_str());
+        out << S;
+                   
+        break;
+      }
+
+      case tDeviceSpecial:
+      {
+        snprintf(S,sizeof(S),"S Mod=%lu",DevSpecial.ModTime+Head.Epoch);
+        out << S;
+        if ((Head.Flags[Tag] & DeviceSpecial::FlPerm) != 0)
+        {
+           snprintf(S,sizeof(S)," Perm=%lo",DevSpecial.Permissions);
+           out << S;
+        }
+        if ((Head.Flags[Tag] & DeviceSpecial::FlOwner) != 0)
+        {
+           snprintf(S,sizeof(S)," U=%lu G=%lu",DevSpecial.User,DevSpecial.Group);
+           out << S;
+        }       
+        snprintf(S,sizeof(S)," N='%s'\n",DevSpecial.Name.c_str());
+        out << S;
+        break;
+      }
+      
+      case tSymlink:
+      {
+        snprintf(S,sizeof(S),"L Mod=%lu",SLink.ModTime+Head.Epoch);
+        out << S;
+        if ((Head.Flags[Tag] & Symlink::FlOwner) != 0)
+        {
+           snprintf(S,sizeof(S)," U=%lu G=%lu",SLink.User,SLink.Group);
+           out << S;
+        }
+        
+        snprintf(S,sizeof(S)," N='%s' T='%s'\n",SLink.Name.c_str(),SLink.To.c_str());
+        out << S;
+        break;
+      }
+        
+      case dsFList::tTrailer:
+      {
+        snprintf(S,sizeof(S),"T Sig=%lx\n",Trail.Signature);
+        out << S;
+        break;
+      }
+
+      case dsFList::tRSyncChecksum:
+      {
+        snprintf(S,sizeof(S),"RC BlockSize=%lu FileSize=%lu\n",RChk.BlockSize,RChk.FileSize);
+        out << S;
+        break;
+      }
+      
+      case dsFList::tAggregateFile:
+      {
+        snprintf(S,sizeof(S),"RAG File='%s'\n",AgFile.File.c_str());
+        break;
+      }
+
+      case tRSyncEnd:
+      out << "RSE" << endl;
+      break;
+      
+      default:
+      return _error->Error("Unknown tag %u",Tag);
+   }
+   return true;
+}
+                                                                       /*}}}*/
+
+// IO::IO - Constructor                                                                        /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsFList::IO::IO()
+{
+   NoStrings = false;
+}
+                                                                       /*}}}*/
+// IO::ReadNum - Read a variable byte number coded with WriteNum       /*{{{*/
+// ---------------------------------------------------------------------
+/* Read a variable byte encoded number, see WriteNum */
+bool dsFList::IO::ReadNum(unsigned long &Number)
+{
+   unsigned int I = 0;
+   Number = 0;
+   while (1)
+   {
+      unsigned char Byte = 0;
+      if (Read(&Byte,1) == false)
+        return false;
+      Number |= (Byte & 0x7F) << 7*I;
+      if ((Byte & (1<<7)) == 0)
+        return true;
+      I++;
+   }   
+}
+                                                                       /*}}}*/
+// IO::WriteNum - Write a variable byte number                         /*{{{*/
+// ---------------------------------------------------------------------
+/* This encodes the given number into a variable number of bytes and writes
+   it to the stream. This is done by encoding it in 7 bit chunks and using
+   the 8th bit as a continuation flag */
+bool dsFList::IO::WriteNum(unsigned long Number)
+{
+   unsigned char Bytes[10];
+   unsigned int I = 0;
+   while (1)
+   {
+      Bytes[I] = Number & 0x7F;
+      Number >>= 7;
+      if (Number != 0)
+        Bytes[I] |= (1<<7);
+      else
+        break;
+      I++;
+   }
+   return Write(Bytes,I+1);
+}
+                                                                       /*}}}*/
+// IO::ReadInt - Read an unsigned int written by WriteInt              /*{{{*/
+// ---------------------------------------------------------------------
+/* Read an unsigned integer of a given number of bytes, see WriteInt */
+bool dsFList::IO::ReadInt(unsigned long &Number,unsigned char Count)
+{
+   unsigned char Bytes[8];
+   if (Read(&Bytes,Count) == false)
+      return false;
+   
+   Number = 0;
+   for (unsigned int I = 0; I != Count; I++)
+      Number |= (Bytes[I] << I*8);
+   return true;
+}
+                                                                       /*}}}*/
+// IO::WriteInt - Write an unsigned int with a number of bytes         /*{{{*/
+// ---------------------------------------------------------------------
+/* This writes the number of bytes in least-significant-byte first order */
+bool dsFList::IO::WriteInt(unsigned long Number,unsigned char Count)
+{
+   unsigned char Bytes[8];
+   for (unsigned int I = 0; I != Count; I++)
+      Bytes[I] = (Number >> I*8);
+   return Write(Bytes,Count);
+}
+                                                                       /*}}}*/
+// IO::ReadInt - Read an signed int written by WriteInt                        /*{{{*/
+// ---------------------------------------------------------------------
+/* Read a signed integer of a given number of bytes, see WriteInt */
+bool dsFList::IO::ReadInt(signed long &Number,unsigned char Count)
+{
+   unsigned char Bytes[8];
+   if (Read(&Bytes,Count) == false)
+      return false;
+   
+   Number = 0;
+   for (unsigned int I = 0; I != Count; I++)
+      Number |= (Bytes[I] << I*8);
+   return true;
+}
+                                                                       /*}}}*/
+// IO::WriteInt - Write an signed int with a number of bytes           /*{{{*/
+// ---------------------------------------------------------------------
+/* This writes the number of bytes in least-significant-byte first order */
+bool dsFList::IO::WriteInt(signed long Number,unsigned char Count)
+{
+   unsigned char Bytes[8];
+   for (unsigned int I = 0; I != Count; I++)
+      Bytes[I] = (Number >> I*8);
+   return Write(Bytes,Count);
+}
+                                                                       /*}}}*/
+// IO::ReadString - Read a string written by WriteString               /*{{{*/
+// ---------------------------------------------------------------------
+/* If NoStrings is set then the string is not allocated into memory, this
+   saves time when scanning a file */
+bool dsFList::IO::ReadString(string &Foo)
+{
+   char S[1024];
+   unsigned long Len;
+   if (ReadNum(Len) == false)
+      return false;
+   if (Len >= sizeof(S))
+      return _error->Error("String buffer too small");   
+   if (Read(S,Len) == false)
+      return false;
+   S[Len] = 0;
+   
+   if (NoStrings == false)
+      Foo = S;
+   else
+      Foo = string();
+   
+   return true;
+}
+                                                                       /*}}}*/
+// IO::WriteString - Write a string to the stream                      /*{{{*/
+// ---------------------------------------------------------------------
+/* Write a string, we encode a Number contianing the length and then the 
+   string itself */
+bool dsFList::IO::WriteString(string const &Foo)
+{
+   return WriteNum(Foo.length()) && Write(Foo.c_str(),strlen(Foo.c_str()));
+}
+                                                                       /*}}}*/
+
+// Header::Header - Constructor                                                /*{{{*/
+// ---------------------------------------------------------------------
+/* The constructor sets the current signature and version information */
+dsFList::Header::Header() : Signature(0x97E78AB), MajorVersion(0), 
+                            MinorVersion(1)
+{
+   Tag = dsFList::tHeader;
+   FlagCount = _count(Flags);
+   memset(Flags,0,sizeof(Flags));
+      
+   Epoch = (unsigned long)time(0);
+}
+                                                                       /*}}}*/
+// Header::Read - Read the coded header                                        /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Header::Read(IO &IO)
+{
+   // Read the contents
+   if ((IO.ReadInt(Signature,4) && 
+       IO.ReadInt(MajorVersion,2) && IO.ReadInt(MinorVersion,2) && 
+       IO.ReadNum(Epoch) && IO.ReadInt(FlagCount,1)) == false)
+      return false;
+
+   unsigned long RealFlagCount = FlagCount;
+   if (FlagCount > _count(Flags))
+      FlagCount = _count(Flags);
+   
+   // Read the flag array
+   for (unsigned int I = 0; I != RealFlagCount; I++)
+   {
+      unsigned long Jnk;
+      if (I >= FlagCount)
+      {
+        if (IO.ReadInt(Jnk,4) == false)
+           return false;
+      }
+      else
+      {
+        if (IO.ReadInt(Flags[I],4) == false)
+           return false;
+      }
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+// Header::Write - Write the coded header                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Header::Write(IO &IO)
+{
+   FlagCount = _count(Flags);
+   
+   // Write the contents
+   if ((IO.WriteInt(Tag,1) && IO.WriteInt(Signature,4) && 
+       IO.WriteInt(MajorVersion,2) && IO.WriteInt(MinorVersion,2) && 
+       IO.WriteNum(Epoch) && IO.WriteInt(FlagCount,1)) == false)
+      return false;
+   
+   // Write the flag array
+   for (unsigned int I = 0; I != FlagCount; I++)
+      if (IO.WriteInt(Flags[I],4) == false)
+        return false;
+   return true;
+}
+                                                                       /*}}}*/
+// Directory::Read - Read a coded directory record                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Directory::Read(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.ReadInt(ModTime,4)) == false)
+      return false;
+   if ((F & FlPerm) == FlPerm && IO.ReadInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.ReadNum(User) && 
+                                   IO.ReadNum(Group)) == false)
+      return false;   
+   if (IO.ReadString(Name) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// Directory::Write - Write a compacted directory record               /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Directory::Write(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.WriteInt(Tag,1) && IO.WriteInt(ModTime,4)) == false)
+      return false;
+   if ((F & FlPerm) == FlPerm && IO.WriteInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.WriteNum(User) && 
+                                   IO.WriteNum(Group)) == false)
+      return false;   
+   if (IO.WriteString(Name) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// NormalFile::Read - Read the compacted file record                   /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::NormalFile::Read(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.ReadInt(ModTime,4)) == false)
+      return false;
+   if ((F & FlPerm) == FlPerm && IO.ReadInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.ReadNum(User) && 
+                                   IO.ReadNum(Group)) == false)
+      return false;   
+   if ((IO.ReadString(Name) && IO.ReadNum(Size)) == false)
+      return false;
+   if ((F & FlMD5) == FlMD5 && IO.Read(&MD5,16) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// NormalFile::write - Write the compacted file record                 /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::NormalFile::Write(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.WriteInt(Tag,1) && IO.WriteInt(ModTime,4)) == false)
+      return false;
+   if ((F & FlPerm) == FlPerm && IO.WriteInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.WriteNum(User) && 
+                                   IO.WriteNum(Group)) == false)
+      return false;   
+   if ((IO.WriteString(Name) && IO.WriteNum(Size)) == false)
+      return false;
+   if ((F & FlMD5) == FlMD5 && IO.Write(&MD5,16) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// Symlink::Read - Read a compacted symlink record                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Symlink::Read(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.ReadInt(ModTime,4)) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.ReadNum(User) &&
+                                   IO.ReadNum(Group)) == false)
+      return false;   
+   if ((IO.ReadString(Name) && IO.ReadInt(Compress,1) &&
+       IO.ReadString(To)) == false)
+      return false;
+
+   // Decompress the string
+   if (Compress != 0)
+   {
+      if ((Compress & (1<<7)) == (1<<7))
+        To += Name;
+      if ((Compress & 0x7F) != 0)
+        To = string(IO.LastSymlink,0,Compress & 0x7F) + To;
+   }
+   
+   IO.LastSymlink = To;
+   return true;
+}
+                                                                       /*}}}*/
+// Symlink::Write - Write a compacted symlink record                   /*{{{*/
+// ---------------------------------------------------------------------
+/* This performs the symlink compression described in the file list
+   document. */
+bool dsFList::Symlink::Write(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.WriteInt(Tag,1) && IO.WriteInt(ModTime,4)) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.WriteNum(User) &&
+                                   IO.WriteNum(Group)) == false)
+      return false;
+   
+   if (IO.WriteString(Name) == false)
+      return false;
+   
+   // Attempt to remove the trailing text
+   bool Trail = false;
+   if (To.length() >= Name.length())
+   {
+      unsigned int I = To.length() - Name.length();
+      for (unsigned int J = 0; I < To.length(); I++, J++)
+        if (To[I] != Name[J])
+           break;
+      if (I == To.length())
+        Trail = true;
+   }
+   
+   // Compress the symlink target
+   Compress = 0;
+   unsigned int Len = To.length();
+   if (Trail == true)
+      Len -= Name.length();
+   for (; Compress < Len && Compress < IO.LastSymlink.length() &&
+       Compress < 0x7F; Compress++)
+      if (To[Compress] != IO.LastSymlink[Compress])
+         break;
+
+   // Set the trail flag
+   if (Trail == true)
+      Compress |= (1<<7);
+   
+   // Write the compresion byte
+   if (IO.WriteInt(Compress,1) == false)
+      return false;
+   
+   // Write the data string
+   if (Trail == true)
+   {
+      if (IO.WriteString(string(To,Compress & 0x7F,To.length() - Name.length() - (Compress & 0x7F))) == false)
+        return false;
+   }
+   else
+   {
+      if (IO.WriteString(string(To,Compress,To.length() - Compress)) == false)
+        return false;
+   }
+   
+   IO.LastSymlink = To;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// DeviceSpecial::Read - Read a compacted device special record                /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::DeviceSpecial::Read(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.ReadInt(ModTime,4)) == false)
+      return false;
+   if (IO.ReadInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.ReadNum(User) &&
+                                   IO.ReadNum(Group)) == false)
+      return false;
+   if ((IO.ReadNum(Dev) && IO.ReadString(Name)) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// DeviceSpecial::Write - Write a compacted device special record      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::DeviceSpecial::Write(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.WriteInt(Tag,1) && IO.WriteInt(ModTime,4)) == false)
+      return false;
+   if (IO.WriteInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.WriteNum(User) &&
+                                   IO.WriteNum(Group)) == false)
+      return false;
+   if ((IO.WriteNum(Dev) && IO.WriteString(Name)) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// Filter::Read - Read a compacted filter record                       /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Filter::Read(IO &IO)
+{
+   if ((IO.ReadInt(Type,1) && 
+       IO.ReadString(Pattern)) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// Filter::Write - Write a compacted filter record                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Filter::Write(IO &IO)
+{
+   if ((IO.WriteInt(Tag,1) && IO.WriteInt(Type,1) &&
+       IO.WriteString(Pattern)) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// UidGidMap::Read - Read a compacted Uid/Gid map record               /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::UidGidMap::Read(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   if ((IO.ReadNum(FileID)) == false)
+      return false;
+   
+   if ((F & FlRealID) == FlRealID && IO.ReadNum(RealID) == false)
+      return false;
+   if (IO.ReadString(Name) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// UidGidMap::Write - Write a compacted Uid/Gid map record             /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::UidGidMap::Write(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   if ((IO.WriteInt(Tag,1) && IO.WriteNum(FileID)) == false)
+      return false;
+   
+   if ((F & FlRealID) == FlRealID && IO.WriteNum(RealID) == false)
+      return false;
+   if (IO.WriteString(Name) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// HardLink::Read - Read the compacted link record                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::HardLink::Read(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.ReadInt(ModTime,4) && IO.ReadNum(Serial)) == false)
+      return false;
+   if ((F & FlPerm) == FlPerm && IO.ReadInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.ReadNum(User) && 
+                                   IO.ReadNum(Group)) == false)
+      return false;   
+   if ((IO.ReadString(Name) && IO.ReadNum(Size)) == false)
+      return false;
+   if ((F & FlMD5) == FlMD5 && IO.Read(&MD5,16) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// HardLink::Write - Write the compacted file record                   /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::HardLink::Write(IO &IO)
+{
+   unsigned long F = IO.Header.Flags[Tag];
+   
+   if ((IO.WriteInt(Tag,1) && IO.WriteInt(ModTime,4) && 
+       IO.ReadNum(Serial)) == false)
+      return false;
+   if ((F & FlPerm) == FlPerm && IO.WriteInt(Permissions,2) == false)
+      return false;
+   if ((F & FlOwner) == FlOwner && (IO.WriteNum(User) && 
+                                   IO.WriteNum(Group)) == false)
+      return false;   
+   if ((IO.WriteString(Name) && IO.WriteNum(Size)) == false)
+      return false;
+   if ((F & FlMD5) == FlMD5 && IO.Write(&MD5,16) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// Trailer::Trailer - Constructor                                      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsFList::Trailer::Trailer() : Tag(dsFList::tTrailer), Signature(0xBA87E79)
+{
+}
+                                                                       /*}}}*/
+// Trailer::Read - Read a compacted tail record                                /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Trailer::Read(IO &IO)
+{
+   if (IO.ReadInt(Signature,4) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// Trailer::Write - Write a compacted tail record                      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::Trailer::Write(IO &IO)
+{
+   if ((IO.WriteInt(Tag,1) &&
+       IO.WriteInt(Signature,4)) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// RSyncChecksum::RSyncChecksum - Constructor                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsFList::RSyncChecksum::RSyncChecksum() : Tag(dsFList::tRSyncChecksum),
+                                          Sums(0)
+{
+}
+                                                                       /*}}}*/
+// RSyncChecksum::~RSyncChecksum - Constructor                         /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsFList::RSyncChecksum::~RSyncChecksum() 
+{
+   delete [] Sums;
+}
+                                                                       /*}}}*/
+// RSyncChecksum::Read - Read a compacted device special record                /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::RSyncChecksum::Read(IO &IO)
+{
+   if ((IO.ReadNum(BlockSize) && IO.ReadNum(FileSize)) == false)
+      return false;
+   
+   // Read in the checksum table
+   delete [] Sums;
+   Sums = new unsigned char[(FileSize + BlockSize-1)/BlockSize*20];
+   if (IO.Read(Sums,(FileSize + BlockSize-1)/BlockSize*20) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// RSyncChecksum::Write - Write a compacted device special record      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::RSyncChecksum::Write(IO &IO)
+{
+   if ((IO.WriteInt(Tag,1) && IO.WriteNum(BlockSize) &&
+       IO.WriteNum(FileSize)) == false)
+      return false;
+   
+   if (IO.Write(Sums,(FileSize + BlockSize-1)/BlockSize*20) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
+// AggregateFile::Read - Read a aggregate file record                  /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::AggregateFile::Read(IO &IO)
+{
+   return IO.ReadString(File);
+}
+                                                                       /*}}}*/
+// AggregateFile::Write - Write a compacted filter record              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsFList::AggregateFile::Write(IO &IO)
+{
+   if ((IO.WriteInt(Tag,1) && IO.WriteString(File)) == false)
+      return false;
+   return true;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/filelist.h b/tools/dsync-0.0/libdsync/filelist.h
new file mode 100644 (file)
index 0000000..430d089
--- /dev/null
@@ -0,0 +1,224 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: filelist.h,v 1.10 1999/12/26 06:59:00 jgg Exp $
+/* ######################################################################
+   
+   File List structures
+   
+   These structures represent the uncompacted binary records from the
+   file list file. Functions are provided to compact and decompact these
+   structures for reading and writing.
+
+   The dsFList class can be instantiated to get get a general 'all records'
+   storage. It also has a member to read the next record from the IO and
+   to print out a record summary.
+   
+   Be sure to read filelist.sgml which contains the precise meaning of 
+   the feilds and the compaction technique used.
+
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_FILELIST
+#define DSYNC_FILELIST
+
+#ifdef __GNUG__
+#pragma interface "dsync/filelist.h"
+#endif 
+
+#include <string>
+using namespace std;
+
+class dsFList
+{
+   public:
+      
+   class IO;
+
+   struct Header
+   {
+      unsigned long Tag;
+      unsigned long Signature;
+      unsigned long MajorVersion;
+      unsigned long MinorVersion;
+      unsigned long Epoch;
+      
+      unsigned long FlagCount;
+      unsigned long Flags[15];
+
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+      
+      Header();
+   };
+   
+   struct DirEntity
+   {
+      unsigned long Tag;
+      signed long ModTime;
+      unsigned long Permissions;
+      unsigned long User;
+      unsigned long Group;
+      string Name;
+
+      enum EntFlags {FlPerm = (1<<0), FlOwner = (1<<1)};
+      
+      /* You know what? egcs-2.91.60 will not call the destructor for Name
+         if this in not here. I can't reproduce this in a simpler context
+         either. - Jgg [time passes] serious egcs bug, it was mislinking
+         the string classes :< */
+      ~DirEntity() {};
+   };
+   
+   struct Directory : public DirEntity
+   {      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+   };
+   
+   struct NormalFile : public DirEntity
+   {
+      unsigned long Size;
+      unsigned char MD5[16];
+      
+      enum Flags {FlMD5 = (1<<2)};
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);      
+   };
+   
+   struct Symlink : public DirEntity
+   {
+      unsigned long Compress;
+      string To;
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+   };
+   
+   struct DeviceSpecial : public DirEntity
+   {
+      unsigned long Dev;
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+   };
+   
+   struct Filter
+   {
+      unsigned long Tag;
+      unsigned long Type;
+      string Pattern;
+      
+      enum Types {Include=1, Exclude=2};
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+   };
+   
+   struct UidGidMap
+   {
+      unsigned long Tag;
+      unsigned long FileID;
+      unsigned long RealID;
+      string Name;
+      
+      enum Flags {FlRealID = (1<<0)};
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+   };
+   
+   struct HardLink : public NormalFile
+   {
+      unsigned long Serial;
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+   };
+   
+   struct Trailer
+   {
+      unsigned long Tag;   
+      unsigned long Signature;
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+      Trailer();
+   };
+   
+   struct RSyncChecksum
+   {
+      unsigned long Tag;
+      unsigned long BlockSize;
+      unsigned long FileSize;
+
+      // Array of 160 bit values (20 bytes) stored in Network byte order
+      unsigned char *Sums;
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+      RSyncChecksum();
+      ~RSyncChecksum();
+   };
+  
+   struct AggregateFile
+   {
+      unsigned long Tag;
+      string File;
+      
+      bool Read(IO &IO);
+      bool Write(IO &IO);
+   };
+  
+   
+   enum Types {tHeader=0, tDirMarker=1, tDirStart=2, tDirEnd=3, tNormalFile=4,
+      tSymlink=5, tDeviceSpecial=6, tDirectory=7, tFilter=8, 
+      tUidMap=9, tGidMap=10, tHardLink=11, tTrailer=12, tRSyncChecksum=13,
+      tAggregateFile=14, tRSyncEnd=15};
+
+   unsigned long Tag;
+   Header Head;
+   Directory Dir;
+   NormalFile NFile;
+   Symlink SLink;
+   DeviceSpecial DevSpecial; 
+   Filter Filt;
+   UidGidMap UMap;
+   HardLink HLink;
+   Trailer Trail;
+   DirEntity *Entity;
+   NormalFile *File;
+   RSyncChecksum RChk;
+   AggregateFile AgFile;
+      
+   bool Step(IO &IO);
+   bool Print(ostream &out);
+};
+
+class dsFList::IO
+{
+   public:
+
+   string LastSymlink;
+   dsFList::Header Header;
+   bool NoStrings;
+   
+   virtual bool Read(void *Buf,unsigned long Len) = 0;
+   virtual bool Write(const void *Buf,unsigned long Len) = 0;
+   virtual bool Seek(unsigned long Bytes) = 0;
+   virtual unsigned long Tell() = 0;
+   
+   bool ReadNum(unsigned long &Number);
+   bool WriteNum(unsigned long Number);
+   bool ReadInt(unsigned long &Number,unsigned char Count);
+   bool WriteInt(unsigned long Number,unsigned char Count);
+   bool ReadInt(signed long &Number,unsigned char Count);
+   bool WriteInt(signed long Number,unsigned char Count);
+   bool ReadString(string &Foo);
+   bool WriteString(string const &Foo);
+   
+   IO();
+   virtual ~IO() {};
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/filelistdb.cc b/tools/dsync-0.0/libdsync/filelistdb.cc
new file mode 100644 (file)
index 0000000..74bf411
--- /dev/null
@@ -0,0 +1,166 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: filelistdb.cc,v 1.4 1999/02/27 08:00:05 jgg Exp $
+/* ######################################################################
+   
+   File List Database
+
+   The mmap class should probably go someplace else..
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/filelistdb.h"
+#endif
+
+#include <dsync/filelistdb.h>
+#include <dsync/error.h>
+                                                                       /*}}}*/
+
+// FileListDB::dsFileListDB - Constructor                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsFileListDB::dsFileListDB()
+{
+}
+                                                                       /*}}}*/
+// FileListDB::Generate - Build the directory map                      /*{{{*/
+// ---------------------------------------------------------------------
+/* This sucks the offset of every directory record into a stl map for 
+   quick lookup. */
+bool dsFileListDB::Generate(dsFList::IO &IO)
+{
+   // Iterate over the file
+   dsFList List;
+   while (List.Step(IO) == true)
+   {
+      // Record the current location so we can jump to it
+      unsigned long Pos = IO.Tell();
+      string LastSymlink = IO.LastSymlink;
+
+      if (List.Tag == dsFList::tTrailer)
+        return true;
+        
+      // We only index directory start records
+      if (List.Tag != dsFList::tDirStart)
+        continue;
+      
+      // Store it in the map
+      Location &Loc = Map[List.Dir.Name];
+      Loc.Offset = Pos;
+      Loc.LastSymlink = LastSymlink;
+   }
+  
+   return false;
+}
+                                                                       /*}}}*/
+// FileListDB::Lookup - Find a directory and file                      /*{{{*/
+// ---------------------------------------------------------------------
+/* We use a caching scheme, if the last lookup is in the same directory
+   we do not re-seek but mearly look at the next entries till termination
+   then wraps around. In the case of a largely unchanged directory this 
+   gives huge speed increases. */
+bool dsFileListDB::Lookup(dsFList::IO &IO,const char *Dir,const char *File,
+                         dsFList &List)
+{
+   map<string,Location>::const_iterator I = Map.find(Dir);
+   if (I == Map.end())
+      return false;
+   
+   // See if we should reseek
+   bool Restart = true;
+   if (LastDir != Dir || LastDir.empty() == true)
+   {
+      Restart = false;
+      IO.LastSymlink = I->second.LastSymlink;
+      if (IO.Seek(I->second.Offset) == false)
+        return false;
+      LastDir = Dir;
+   }
+
+   List.Head = IO.Header;
+   while (List.Step(IO) == true)
+   {
+      // Oops, ran out of directories
+      if (List.Tag == dsFList::tDirEnd ||
+         List.Tag == dsFList::tDirStart ||
+         List.Tag == dsFList::tTrailer)
+      {
+        if (Restart == false)
+        {
+           LastDir = string();
+           return false;
+        }
+        
+        Restart = false;
+        IO.LastSymlink = I->second.LastSymlink;
+        if (IO.Seek(I->second.Offset) == false)
+           return false;
+        LastDir = Dir;
+
+        continue;
+      }
+      
+      // Skip over non directory contents
+      if (List.Tag == dsFList::tDirMarker ||
+         List.Tag == dsFList::tDirEnd ||
+         List.Tag == dsFList::tDirStart ||
+         List.Entity == 0)
+        continue;
+
+      if (List.Entity->Name == File)
+        return true;
+   }
+   return false;
+}
+                                                                       /*}}}*/
+
+// MMapIO::dsMMapIO - Constructor                                      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsMMapIO::dsMMapIO(string File) : Fd(File,FileFd::ReadOnly), 
+              Map(Fd,MMap::Public | MMap::ReadOnly)
+{
+   Pos = 0;
+}
+                                                                       /*}}}*/
+// MMapIO::Read - Read bytes from the map                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsMMapIO::Read(void *Buf,unsigned long Len)
+{
+   if (Pos + Len > Map.Size())
+      return _error->Error("Attempt to read past end of mmap");
+   memcpy(Buf,(unsigned char *)Map.Data() + Pos,Len);
+   Pos += Len;
+   return true;
+}
+                                                                       /*}}}*/
+// MMapIO::Write - Write bytes (fail)                                  /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsMMapIO::Write(const void *Buf,unsigned long Len)
+{
+   return _error->Error("Attempt to write to read only mmap");
+}
+                                                                       /*}}}*/
+// MMapIO::Seek - Jump to a spot                                       /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsMMapIO::Seek(unsigned long Bytes)
+{
+   if (Bytes > Map.Size())
+      return _error->Error("Attempt to seek past end of mmap");
+   Pos = Bytes;
+   return true;
+}
+                                                                       /*}}}*/
+// MMapIO::Tell - Return the current location                          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+unsigned long dsMMapIO::Tell()
+{
+   return Pos;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/filelistdb.h b/tools/dsync-0.0/libdsync/filelistdb.h
new file mode 100644 (file)
index 0000000..9594257
--- /dev/null
@@ -0,0 +1,63 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: filelistdb.h,v 1.2 1999/01/10 07:34:05 jgg Exp $
+/* ######################################################################
+   
+   File List DB
+   
+   This scans a file list and generates a searchable list of all 
+   directories in the list. It can then do a lookup of a given file,
+   directory pair.
+   
+   The memory mapped IO class is recommended for use with the DB class 
+   for speed.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_FILELISTDB
+#define DSYNC_FILELISTDB
+
+#ifdef __GNUG__
+#pragma interface "dsync/filelistdb.h"
+#endif 
+
+#include <dsync/filelist.h>
+#include <dsync/mmap.h>
+#include <map>
+
+class dsFileListDB
+{
+   struct Location
+   {
+      unsigned long Offset;
+      string LastSymlink;
+   };
+   
+   dsFList::IO *IO;
+   map<string,Location> Map;
+   string LastDir;
+   public:
+
+   bool Generate(dsFList::IO &IO);
+   bool Lookup(dsFList::IO &IO,const char *Dir,const char *File,dsFList &List);
+   
+   dsFileListDB();
+};
+
+class dsMMapIO : public dsFList::IO
+{
+   FileFd Fd;
+   MMap Map;
+   unsigned long Pos;
+   
+   public:
+   
+   virtual bool Read(void *Buf,unsigned long Len);
+   virtual bool Write(const void *Buf,unsigned long Len);
+   virtual bool Seek(unsigned long Bytes);
+   virtual unsigned long Tell();
+   
+   dsMMapIO(string File);
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/genfilelist.cc b/tools/dsync-0.0/libdsync/genfilelist.cc
new file mode 100644 (file)
index 0000000..7c5b10a
--- /dev/null
@@ -0,0 +1,574 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: genfilelist.cc,v 1.10 1999/12/26 06:59:01 jgg Exp $
+/* ######################################################################
+   
+   Generate File List 
+
+   File list generation can be done with modification to the generation
+   order, ordering can be done by depth, breadth or by tree with and
+   a fitler can be applied to delay a directory till the end of processing.
+   
+   The emitter simply generates the necessary structure and writes it to
+   the IO. The client can hook some of the functions to provide progress
+   reporting and md5 caching if so desired.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/genfilelist.h"
+#endif
+
+#include <dsync/genfilelist.h>
+#include <dsync/error.h>
+#include <dsync/fileutl.h>
+#include <dsync/md5.h>
+#include <dsync/fileutl.h>
+#include <dsync/rsync-algo.h>
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdio.h>
+                                                                       /*}}}*/
+
+// GenFileList::dsGenFileList - Constructor                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsGenFileList::dsGenFileList() : IO(0), Type(Tree)
+{
+}
+                                                                       /*}}}*/
+// GenFileList::~dsGenFileList - Destructor                            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+dsGenFileList::~dsGenFileList()
+{
+}
+                                                                       /*}}}*/
+// GenFileList::Go - Generate the list                                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This invokes the proper recursive directory scanner to build the file
+   names. Depth and Breath use a queue */
+bool dsGenFileList::Go(string Base,dsFList::IO &IO)
+{
+   // Setup the queues and store the current directory
+   string StartDir = SafeGetCWD();
+   Queue.erase(Queue.begin(),Queue.end());
+   DelayQueue.erase(Queue.begin(),Queue.end());
+
+   struct stat St;
+   if (stat(Base.c_str(),&St) != 0)
+      return _error->Errno("stat","Could not stat the base directory");
+   
+   // Begin
+   this->IO = &IO;
+   IO.Header.Write(IO);
+   
+   switch (Type)
+   {
+      case Depth:
+      {
+        // Change to the base directory
+        if (chdir(Base.c_str()) != 0)
+           return _error->Errno("chdir","Could not change to %s",Base.c_str());
+        Base = SafeGetCWD();
+        
+        char Cwd[1024];
+        Cwd[0] = 0;
+        if (DirDepthFirst(Cwd) == false)
+        {
+           chdir(StartDir.c_str());
+           return false;
+        }
+
+        // Now deal with the delay list
+        while (DelayQueue.empty() == false)
+        {
+           // Get the first delayed directory
+           string Dir = DelayQueue.front();
+           DelayQueue.pop_front();
+           
+           // Change to it and emit it.
+           strcpy(Cwd,Dir.c_str());
+           chdir(Base.c_str());
+           chdir(Cwd);
+           if (DirDepthFirst(Cwd) == false)
+           {
+              chdir(StartDir.c_str());
+              return false;
+           }       
+        }
+        
+        break;
+      }
+      
+      case Tree:
+      case Breadth:
+      {
+        // Change to the base directory
+        if (chdir(Base.c_str()) != 0)
+           return _error->Errno("chdir","Could not change to %s",Base.c_str());
+        Base = SafeGetCWD();
+
+        Queue.push_back("");
+        while (Queue.empty() == false || DelayQueue.empty() == false)
+        {
+           if (DirTree() == false)
+           {
+              chdir(StartDir.c_str());
+              return false;
+           }
+
+           chdir(Base.c_str());
+        }
+        break;
+      }
+
+      default:
+      return _error->Error("Internal Error");
+   }; 
+
+   chdir(StartDir.c_str());
+   
+   dsFList::Trailer Trail;
+   return Trail.Write(IO);
+}
+                                                                       /*}}}*/
+// GenFileList::DirDepthFirst - Depth first directory ordering         /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool dsGenFileList::DirDepthFirst(char *CurDir)
+{
+   // Scan the directory, first pass is to descend into the sub directories
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+      return _error->Errno("opendir","Unable to open direcotry %s",CurDir);
+   struct dirent *Ent;
+   bool EmittedThis = false;
+   struct stat St;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+      
+      if (lstat(Ent->d_name,&St) != 0)
+      {
+        closedir(DirSt);
+        return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
+      }
+      
+      // it is a directory
+      if (S_ISDIR(St.st_mode) != 0)
+      {
+        char S[1024];
+        snprintf(S,sizeof(S),"%s/",Ent->d_name);
+        
+        // Check the Filter
+        if (Filter.Test(CurDir,S) == false)
+           continue;
+
+        // Emit a directory marker record for this directory
+        if (EmittedThis == false)
+        {
+           EmittedThis = true;
+
+           if (lstat(".",&St) != 0)
+           {
+              closedir(DirSt);
+              return _error->Errno("stat","Could not stat %s",CurDir);
+           }
+           
+           if (DirectoryMarker(CurDir,St) == false)
+           {
+              closedir(DirSt);
+              return false;
+           }       
+        }
+
+        // Check the delay filter
+        if (PreferFilter.Test(CurDir,S) == false)
+        {
+           snprintf(S,sizeof(S),"%s%s/",CurDir,Ent->d_name);
+           DelayQueue.push_back(S);        
+           continue;
+        }
+        
+        // Append the new directory to CurDir and decend
+        char *End = CurDir + strlen(CurDir);
+        strcat(End,S);
+        if (chdir(S) != 0)
+        {
+           closedir(DirSt);
+           return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
+        }
+        
+        // Recurse
+        if (DirDepthFirst(CurDir) == false)
+        {
+           closedir(DirSt);
+           return false;
+        }
+
+        if (chdir("..") != 0)
+        {
+           closedir(DirSt);
+           return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
+        }
+        
+        // Chop off the directory we added to the current dir
+        *End = 0;
+      }
+   }
+   rewinddir(DirSt);
+
+   // Begin emitting this directory
+   if (lstat(".",&St) != 0)
+   {
+      closedir(DirSt);
+      return _error->Errno("stat","Could not stat %s",CurDir);
+   }
+   
+   if (EnterDir(CurDir,St) == false)
+   {
+      closedir(DirSt);
+      return false;
+   }
+      
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+      
+      struct stat St;
+      if (lstat(Ent->d_name,&St) != 0)
+      {
+        closedir(DirSt);
+        return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
+      }
+      
+      // it is a directory
+      if (S_ISDIR(St.st_mode) != 0)
+      {
+        char S[1024];
+        snprintf(S,sizeof(S),"%s/",Ent->d_name);
+        
+        // Check the Filter
+        if (Filter.Test(CurDir,S) == false)
+           continue;
+      }
+      else
+      {
+        // Check the Filter
+        if (Filter.Test(CurDir,Ent->d_name) == false)
+           continue;
+      }
+      
+      if (DoFile(CurDir,Ent->d_name,St) == false)
+      {
+        closedir(DirSt);
+        return false;
+      }      
+   }
+   closedir(DirSt);
+   
+   if (LeaveDir(CurDir) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// GenFileList::DirTree - Breadth/Tree directory ordering              /*{{{*/
+// ---------------------------------------------------------------------
+/* Breadth ordering does all of the dirs at each depth before proceeding 
+   to the next depth. We just treat the list as a queue to get this
+   effect. Tree ordering does things in a more normal recursive fashion,
+   we treat the queue as a stack to get that effect. */
+bool dsGenFileList::DirTree()
+{
+   string Dir;
+   if (Queue.empty() == false)
+   {
+      Dir = Queue.front();
+      Queue.pop_front();
+   }
+   else
+   {
+      Dir = DelayQueue.front();
+      DelayQueue.pop_front();
+   }
+   
+   struct stat St;
+   if (Dir.empty() == false && chdir(Dir.c_str()) != 0 || stat(".",&St) != 0)
+      return _error->Errno("chdir","Could not change to %s",Dir.c_str());
+
+   if (EnterDir(Dir.c_str(),St) == false)
+      return false;
+   
+   // Scan the directory
+   DIR *DirSt = opendir(".");
+   if (DirSt == 0)
+      return _error->Errno("opendir","Unable to open direcotry %s",Dir.c_str());
+   struct dirent *Ent;
+   while ((Ent = readdir(DirSt)) != 0)
+   {
+      // Skip . and ..
+      if (strcmp(Ent->d_name,".") == 0 ||
+         strcmp(Ent->d_name,"..") == 0)
+        continue;
+      
+      if (lstat(Ent->d_name,&St) != 0)
+      {
+        closedir(DirSt);
+        return _error->Errno("stat","Could not stat %s%s",Dir.c_str(),Ent->d_name);
+      }
+      
+      // It is a directory
+      if (S_ISDIR(St.st_mode) != 0)
+      {
+        char S[1024];
+        snprintf(S,sizeof(S),"%s/",Ent->d_name);
+        
+        // Check the Filter
+        if (Filter.Test(Dir.c_str(),S) == false)
+           continue;
+
+        // Check the delay filter
+        if (PreferFilter.Test(Dir.c_str(),S) == false)
+        {
+           snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
+           if (Type == Tree)
+              DelayQueue.push_front(S);
+           else
+              DelayQueue.push_back(S);     
+           continue;
+        }
+        
+        snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
+        
+        if (Type == Tree)
+           Queue.push_front(S);
+        else
+           Queue.push_back(S);
+      }
+      else
+      {
+        // Check the Filter
+        if (Filter.Test(Dir.c_str(),Ent->d_name) == false)
+           continue;
+      }
+      
+      if (DoFile(Dir.c_str(),Ent->d_name,St) == false)
+      {
+        closedir(DirSt);
+        return false;
+      }      
+   }
+   closedir(DirSt);
+   
+   if (LeaveDir(Dir.c_str()) == false)
+      return false;
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+// GenFileList::EnterDir - Called when a directory is entered          /*{{{*/
+// ---------------------------------------------------------------------
+/* This is called to start a directory block the current working dir
+   should be set to the directory entered. This emits the directory start
+   record */
+bool dsGenFileList::EnterDir(const char *Dir,struct stat const &St)
+{
+   if (Visit(Dir,0,St) != 0)
+      return false;
+
+   dsFList::Directory D;
+   D.Tag = dsFList::tDirStart;
+   D.ModTime = St.st_mtime - IO->Header.Epoch;
+   D.Permissions = St.st_mode & ~S_IFMT;
+   D.Name = Dir;
+   return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
+      D.Write(*IO);    
+}
+                                                                       /*}}}*/
+// GenFileList::LeaveDir - Called when a directory is left             /*{{{*/
+// ---------------------------------------------------------------------
+/* Don't do anything for now */
+bool dsGenFileList::LeaveDir(const char *Dir)
+{
+   return true;
+}
+                                                                       /*}}}*/
+// GenFileList::DirectoryMarker - Called when a dir is skipped         /*{{{*/
+// ---------------------------------------------------------------------
+/* This is used by the depth first ordering, when a dir is temporarily
+   skipped over this function is called to emit a marker */
+bool dsGenFileList::DirectoryMarker(const char *Dir,
+                                   struct stat const &St)
+{
+   dsFList::Directory D;
+   D.Tag = dsFList::tDirMarker;
+   D.ModTime = St.st_mtime - IO->Header.Epoch;
+   D.Permissions = St.st_mode & ~S_IFMT;
+   D.Name = Dir;
+   return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
+      D.Write(*IO);    
+}
+                                                                       /*}}}*/
+// GenFileList::DoFile - This does all other items in a directory      /*{{{*/
+// ---------------------------------------------------------------------
+/* The different file types are emitted as perscribed by the file list
+   document */
+bool dsGenFileList::DoFile(const char *Dir,const char *File,
+                          struct stat const &St)
+{
+   int Res = Visit(Dir,File,St);
+   if (Res < 0)
+      return false;
+   if (Res > 0)
+      return true;
+   
+   // Regular file
+   if (S_ISREG(St.st_mode) != 0)
+   {
+      dsFList::NormalFile F;
+      
+      F.Tag = dsFList::tNormalFile;
+      F.ModTime = St.st_mtime - IO->Header.Epoch;
+      F.Permissions = St.st_mode & ~S_IFMT;
+      F.Name = File;
+      F.Size = St.st_size;
+
+      if (EmitOwner(St,F.User,F.Group,F.Tag,dsFList::NormalFile::FlOwner) == false)
+        return false;
+      
+      // See if we need to emit rsync checksums
+      if (NeedsRSync(Dir,File,F) == true)
+      {
+        dsFList::RSyncChecksum Ck;
+        if (EmitRSync(Dir,File,St,F,Ck) == false)
+           return false;
+
+        // Write out the file record, the checksums and the end marker
+        return F.Write(*IO) && Ck.Write(*IO);
+      }
+      else
+      {
+        if (EmitMD5(Dir,File,St,F.MD5,F.Tag,
+                    dsFList::NormalFile::FlMD5) == false)
+           return false;
+      
+        return F.Write(*IO);
+      }      
+   }
+   
+   // Directory
+   if (S_ISDIR(St.st_mode) != 0)
+   {
+      dsFList::Directory D;
+      D.Tag = dsFList::tDirectory;
+      D.ModTime = St.st_mtime - IO->Header.Epoch;
+      D.Permissions = St.st_mode & ~S_IFMT;
+      D.Name = File;
+      return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) && 
+        D.Write(*IO);    
+   }
+
+   // Link
+   if (S_ISLNK(St.st_mode) != 0)
+   {
+      dsFList::Symlink L;
+      L.Tag = dsFList::tSymlink;
+      L.ModTime = St.st_mtime - IO->Header.Epoch;
+      L.Name = File;
+
+      char Buf[1024];
+      int Res = readlink(File,Buf,sizeof(Buf));
+      if (Res <= 0)
+        return _error->Errno("readlink","Unable to read symbolic link");
+      Buf[Res] = 0;
+      L.To = Buf;
+
+      return EmitOwner(St,L.User,L.Group,L.Tag,dsFList::Symlink::FlOwner) && 
+        L.Write(*IO);    
+   }
+   
+   // Block special file
+   if (S_ISCHR(St.st_mode) != 0 || S_ISBLK(St.st_mode) != 0 || 
+       S_ISFIFO(St.st_mode) != 0)
+   {
+      dsFList::DeviceSpecial D;
+      D.Tag = dsFList::tDeviceSpecial;
+      D.ModTime = St.st_mtime - IO->Header.Epoch;
+      D.Permissions = St.st_mode & ~S_IFMT;
+      D.Dev = St.st_dev;
+      D.Name = File;
+      
+      return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::DeviceSpecial::FlOwner) && 
+        D.Write(*IO);
+   }
+   
+   return _error->Error("File %s%s is not a known type",Dir,File);
+}
+                                                                       /*}}}*/
+// GenFileList::EmitOwner - Set the entitiy ownership                  /*{{{*/
+// ---------------------------------------------------------------------
+/* This emits the necessary UID/GID mapping records and sets the feilds
+   in */
+bool dsGenFileList::EmitOwner(struct stat const &St,unsigned long &UID,
+                             unsigned long &GID,unsigned int Tag,
+                             unsigned int Flag)
+{
+   if ((IO->Header.Flags[Tag] & Flag) != Flag)
+      return true;
+   
+   return _error->Error("UID/GID storage is not supported yet");
+}
+                                                                       /*}}}*/
+// GenFileList::EmitMd5 - Generate the md5 hash for the file           /*{{{*/
+// ---------------------------------------------------------------------
+/* This uses the MD5 class to generate the md5 hash for the entry. */
+bool dsGenFileList::EmitMD5(const char *Dir,const char *File,
+                           struct stat const &St,unsigned char MD5[16],
+                           unsigned int Tag,unsigned int Flag)
+{
+   if ((IO->Header.Flags[Tag] & Flag) != Flag)
+      return true;
+
+   // Open the file
+   MD5Summation Sum;
+   FileFd Fd(File,FileFd::ReadOnly);
+   if (_error->PendingError() == true)
+      return _error->Error("MD5 generation failed for %s%s",Dir,File);
+
+   if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
+      return _error->Error("MD5 generation failed for %s%s",Dir,File);
+   
+   Sum.Result().Value(MD5);
+   
+   return true;
+}
+                                                                       /*}}}*/
+// GenFileList::EmitRSync - Emit a RSync checksum record               /*{{{*/
+// ---------------------------------------------------------------------
+/* This just generates the checksum into the memory structure. */
+bool dsGenFileList::EmitRSync(const char *Dir,const char *File,
+                             struct stat const &St,dsFList::NormalFile &F,
+                             dsFList::RSyncChecksum &Ck)
+{
+   FileFd Fd(File,FileFd::ReadOnly);
+   if (_error->PendingError() == true)
+      return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
+   
+   if (GenerateRSync(Fd,Ck,F.MD5) == false)
+      return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
+   
+   return true;
+}
+                                                                       /*}}}*/
diff --git a/tools/dsync-0.0/libdsync/genfilelist.h b/tools/dsync-0.0/libdsync/genfilelist.h
new file mode 100644 (file)
index 0000000..65f54c1
--- /dev/null
@@ -0,0 +1,74 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: genfilelist.h,v 1.5 1999/12/26 06:59:01 jgg Exp $
+/* ######################################################################
+   
+   Generate File List 
+   
+   This class is responsible for generating the file list. It is fairly
+   simple and direct. One hook is provided to allow a derived class to
+   cache md5 generation.
+   
+   The file list format is documented in the filelist.sgml document.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_GENFILELIST
+#define DSYNC_GENFILELIST
+
+#ifdef __GNUG__
+#pragma interface "dsync/genfilelist.h"
+#endif 
+
+#include <dsync/filefilter.h>
+#include <dsync/filelist.h>
+#include <list>
+
+class dsGenFileList
+{
+   protected:
+   
+   list<string> Queue;
+   list<string> DelayQueue;
+   dsFList::IO *IO;
+   
+   // Hooks
+   virtual int Visit(const char *Directory,const char *File,
+                    struct stat const &Stat) {return 0;};
+      
+   // Directory handlers
+   bool DirDepthFirst(char *CurDir);
+   bool DirTree();
+   // Emitters
+   bool EnterDir(const char *Dir,struct stat const &St);
+   bool LeaveDir(const char *Dir);
+   bool DirectoryMarker(const char *Dir,struct stat const &St);
+   bool DoFile(const char *Dir,const char *File,struct stat const &St);
+
+   bool EmitOwner(struct stat const &St,unsigned long &UID,
+                 unsigned long &GID,unsigned int Tag,unsigned int Flag);
+   virtual bool EmitMD5(const char *Dir,const char *File,
+                       struct stat const &St,unsigned char MD5[16],
+                       unsigned int Tag,unsigned int Flag);
+
+   virtual bool NeedsRSync(const char *Dir,const char *File,
+                          dsFList::NormalFile &F) {return false;};
+   virtual bool EmitRSync(const char *Dir,const char *File,
+                         struct stat const &St,dsFList::NormalFile &F,
+                         dsFList::RSyncChecksum &Ck);
+      
+   public:
+   
+   // Configurable things
+   enum {Depth,Breadth,Tree} Type;
+   dsFileFilter Filter;
+   dsFileFilter PreferFilter;
+   
+   bool Go(string Base,dsFList::IO &IO);
+
+   dsGenFileList();
+   virtual ~dsGenFileList();
+};
+
+#endif
diff --git a/tools/dsync-0.0/libdsync/makefile b/tools/dsync-0.0/libdsync/makefile
new file mode 100644 (file)
index 0000000..7ce9b91
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- make -*-
+BASE=..
+SUBDIR=libdsync
+
+# Header location
+SUBDIRS = contrib
+HEADER_TARGETDIRS = dsync
+
+# Bring in the default rules
+include ../buildlib/defaults.mak
+
+# The library name
+LIBRARY=dsync
+MAJOR=0.0
+MINOR=0
+SLIBS=$(PTHREADLIB)
+
+# Source code for the contributed non-core things
+SOURCE = contrib/error.cc contrib/fileutl.cc contrib/strutl.cc \
+         contrib/configuration.cc contrib/cmndline.cc \
+        contrib/md5.cc contrib/md4.cc contrib/mmap.cc contrib/bitmap.cc \
+        contrib/slidingwindow.cc
+
+# Source code for the main library
+SOURCE+= filefilter.cc genfilelist.cc filelist.cc filelistdb.cc compare.cc \
+         rsync-algo.cc
+
+# Public header files
+HEADERS = error.h configuration.h cmndline.h md5.h md4.h fileutl.h mmap.h \
+          filefilter.h genfilelist.h filelist.h filelistdb.h compare.h \
+          strutl.h bitmap.h slidingwindow.h rsync-algo.h
+
+HEADERS := $(addprefix dsync/,$(HEADERS))
+
+# Private header files
+HEADERS+= system.h 
+
+include $(LIBRARY_H)
diff --git a/tools/dsync-0.0/libdsync/rsync-algo.cc b/tools/dsync-0.0/libdsync/rsync-algo.cc
new file mode 100644 (file)
index 0000000..7b513b3
--- /dev/null
@@ -0,0 +1,205 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: rsync-algo.cc,v 1.3 1999/12/26 06:59:01 jgg Exp $
+/* ######################################################################
+   
+   RSync Algorithrim
+   
+   The RSync algorithim is attributed to Andrew Tridgell and is a means
+   for matching blocks between two streams.  The algorithrim implemented 
+   here differs slightly in its structure and is carefully optimized to be 
+   able to operate on very large files effectively.
+
+   We rely on the RSync rolling weak checksum routine and the MD4 strong 
+   checksum routine. This implementation requires a uniform block size 
+   for each run.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+// Include files                                                       /*{{{*/
+#ifdef __GNUG__
+#pragma implementation "dsync/rsync-algo.h"
+#endif
+
+#include <dsync/rsync-algo.h>
+#include <dsync/error.h>
+#include <dsync/slidingwindow.h>
+#include <dsync/md5.h>
+#include <dsync/md4.h>
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+                                                                       /*}}}*/
+
+// RollingChecksum - Compute the checksum perscribed by rsync          /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+static inline unsigned long RollingChecksum(unsigned char *Start,
+                                           unsigned char *End)
+{
+   unsigned long A = 0;
+   unsigned long B = 0;
+
+   /* A = sum(X[i],j,k)  B = sum((k-j+1)*X[i],j,k);
+      Which reduces to the recurrence, B = sum(A[I],j,k); */
+   for (; Start != End; Start++)
+   {
+      A += *Start;
+      B += A;
+   }
+
+   return (A & 0xFFFF) | (B << 16);
+}
+                                                                       /*}}}*/
+// GenerateRSync - Compute the rsync blocks for a file                 /*{{{*/
+// ---------------------------------------------------------------------
+/* This function generates the RSync checksums for each uniform block in 
+   the file. */
+bool GenerateRSync(FileFd &Fd,dsFList::RSyncChecksum &Ck,
+                  unsigned char OutMD5[16],
+                  unsigned long BlockSize)
+{
+   SlidingWindow Wind(Fd);
+   MD5Summation MD5;
+   
+   Ck.Tag = dsFList::tRSyncChecksum;
+   Ck.BlockSize = BlockSize;
+   Ck.FileSize = Fd.Size();
+   
+   // Allocate sum storage space
+   delete [] Ck.Sums;
+   Ck.Sums = new unsigned char[(Ck.FileSize + BlockSize-1)/BlockSize*20];
+
+   // Slide over the file
+   unsigned char *Start = 0;
+   unsigned char *End = 0;
+   unsigned char *Sum = Ck.Sums;
+   unsigned char *SumEnd = Sum + (Ck.FileSize + BlockSize-1)/BlockSize*20;
+   while (Sum < SumEnd)
+   {
+      // Tail little bit of the file
+      if ((unsigned)(End - Start) < BlockSize)
+      {
+        unsigned char *OldEnd = End;
+        if (Wind.Extend(Start,End) == false)
+           return false;
+        
+        // The file is very small, pretend this is the last block
+        if ((unsigned)(End - Start) < BlockSize && End != Start)
+        {
+           OldEnd = End;
+           End = Start;
+        }
+        
+        // All Done
+        if (Start == End)
+        {
+           /* The last block is rather artifical but can be of use in some
+              cases. Just remember not to insert it into the hash
+              search table!! */
+           *(uint32_t *)Sum = htonl(0xDEADBEEF);
+           InitMD4(Sum+4);
+           ComputeMD4Final(Sum+4,Start,OldEnd,OldEnd-Start);
+           MD5.Add(Start,OldEnd);
+           Sum += 20;
+           break;
+        }
+      }
+
+      // Compute the checksums
+      MD5.Add(Start,Start+BlockSize);
+      *(uint32_t *)Sum = htonl(RollingChecksum(Start,Start+BlockSize));
+      InitMD4(Sum+4);
+      ComputeMD4Final(Sum+4,Start,Start+BlockSize,BlockSize);
+      Sum += 20;
+      
+      Start += BlockSize;
+   }
+   
+   if (Sum != SumEnd)
+      return _error->Error("Size Mismatch generating checksums");
+   
+   MD5.Result().Value(OutMD5);
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+// RSyncMatch::RSyncMatch - Constructor                                        /*{{{*/
+// ---------------------------------------------------------------------
+/* This generates the btree and hash table for looking up checksums */
+RSyncMatch::RSyncMatch(dsFList::RSyncChecksum const &Ck) : Fast(1 << 16), 
+                                    Ck(Ck)
+{
+   Indexes = 0;
+   unsigned int Blocks = (Ck.FileSize + Ck.BlockSize-1)/Ck.BlockSize;
+   
+   // Drop the last partial block from the hashing
+   if (Blocks < 3)
+      return;
+   Blocks--;
+   
+   // Setup the index table
+   Indexes = new uint32_t *[Blocks];
+   IndexesEnd = Indexes + Blocks;
+   
+   // Ready the checksum pointers
+   unsigned char *Sum = Ck.Sums;
+   unsigned char *SumEnd = Sum + Blocks*20;
+   for (uint32_t **I = Indexes; Sum < SumEnd; Sum += 20)
+   {
+      *I++ = (uint32_t *)Sum;
+   }
+   
+   // Sort them
+   qsort(Indexes,Blocks,sizeof(*Indexes),Sort);
+   
+   // Generate the hash table
+   unsigned int Cur = 0;
+   Hashes[Cur] = Indexes;
+   for (uint32_t **I = Indexes; I != IndexesEnd; I++)
+   {
+      printf("%x\n",**I);
+      Fast.Set((**I) >> 16);
+      while (((**I) >> 24) > Cur)
+        Hashes[Cur++] = I;
+   }  
+   while (Cur <= 256)
+      Hashes[Cur++] = IndexesEnd;
+
+   for (unsigned int Cur = 1; Cur != 255; Cur++)
+   {
+      printf("%u %p %x\n",Hashes[Cur] - Hashes[Cur-1],Hashes[Cur],**Hashes[Cur] >> 24);
+   }
+}
+                                                                       /*}}}*/
+// RSyncMatch::~RSyncMatch - Destructor                                        /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+RSyncMatch::~RSyncMatch()
+{
+   delete [] Indexes;
+}
+                                                                       /*}}}*/
+// RSyncMatch::Sort - QSort function                                   /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+int RSyncMatch::Sort(const void *L,const void *R)
+{
+   if (**(uint32_t **)L == **(uint32_t **)R)
+      return 0;
+   if (**(uint32_t **)L > **(uint32_t **)R)
+      return 1;
+   return -1;
+}
+                                                                       /*}}}*/
+bool RSyncMatch::Scan(FileFd &Fd)
+{
+   for (unsigned int Cur = 1; Cur != 256; Cur++)
+   {
+      printf("%u %p\n",Hashes[Cur] - Hashes[Cur-1],Hashes[Cur]);
+   }
+   
+   return true;
+}
diff --git a/tools/dsync-0.0/libdsync/rsync-algo.h b/tools/dsync-0.0/libdsync/rsync-algo.h
new file mode 100644 (file)
index 0000000..1a9711d
--- /dev/null
@@ -0,0 +1,59 @@
+// -*- mode: cpp; mode: fold -*-
+// Description                                                         /*{{{*/
+// $Id: rsync-algo.h,v 1.3 1999/12/26 06:59:01 jgg Exp $
+/* ######################################################################
+   
+   RSync Algorithrim
+   
+   The RSync algorithim is attributed to Andrew Tridgell and is a means
+   for matching blocks between two streams.  The algorithrim implemented 
+   here differs slightly in its structure and is carefully optimized to be 
+   able to operate on very large files effectively.
+
+   We rely on the RSync rolling weak checksum routine and the MD4 strong 
+   checksum routine. This implementation requires a uniform block size 
+   for each run.
+   
+   ##################################################################### */
+                                                                       /*}}}*/
+#ifndef DSYNC_RSYNC_ALGO_H
+#define DSYNC_RSYNC_ALGO_H
+
+#ifdef __GNUG__
+#pragma interface "dsync/rsync-algo.h"
+#endif 
+
+#include <dsync/fileutl.h>
+#include <dsync/filelist.h>
+#include <dsync/bitmap.h>
+
+#include <inttypes.h>
+
+class RSyncMatch
+{
+   uint32_t **Indexes;
+   uint32_t **IndexesEnd;
+   uint32_t **Hashes[257];
+   BitmapVector Fast;
+   dsFList::RSyncChecksum const &Ck;
+
+   static int Sort(const void *L,const void *R);
+   
+   protected:
+   
+   virtual bool Hit(unsigned long Block,off_t SrcOff,
+                   const unsigned char *Data) {return true;};
+   
+   public:
+
+   bool Scan(FileFd &Fd);
+      
+   RSyncMatch(dsFList::RSyncChecksum const &Ck);
+   virtual ~RSyncMatch();
+};
+
+bool GenerateRSync(FileFd &Fd,dsFList::RSyncChecksum &Ck,
+                  unsigned char MD5[16],
+                  unsigned long BlockSize = 8*1024);
+
+#endif
diff --git a/tools/dsync-0.0/test/fftest.cc b/tools/dsync-0.0/test/fftest.cc
new file mode 100644 (file)
index 0000000..aa4adb7
--- /dev/null
@@ -0,0 +1,32 @@
+#include <dsync/cmndline.h>
+#include <dsync/error.h>
+#include <dsync/filefilter.h>
+
+int main(int argc, const char *argv[])
+{
+   CommandLine::Args Args[] = {
+      {'i',"include","filter:: + ",CommandLine::HasArg},
+      {'e',"exclude","filter:: - ",CommandLine::HasArg},
+      {'c',"config-file",0,CommandLine::ConfigFile},
+      {'o',"option",0,CommandLine::ArbItem},
+      {0,0,0,0}};
+   CommandLine CmdL(Args,_config);
+   if (CmdL.Parse(argc,argv) == false)
+   {
+      _error->DumpErrors();
+      return 100;
+   }
+   
+   _config->Dump();
+   
+   dsFileFilter Filt;
+   if (Filt.LoadFilter(_config->Tree("filter")) == false)
+   {
+      _error->DumpErrors();
+      return 100;
+   }
+
+   cout << "Test: " << Filt.Test(CmdL.FileList[0],CmdL.FileList[1]) << endl;
+      
+   return 0;
+}
diff --git a/tools/dsync-0.0/test/makefile b/tools/dsync-0.0/test/makefile
new file mode 100644 (file)
index 0000000..f64d691
--- /dev/null
@@ -0,0 +1,18 @@
+# -*- make -*-
+BASE=..
+SUBDIR=test
+
+# Bring in the default rules
+include ../buildlib/defaults.mak
+
+# Program to test the File Filter
+PROGRAM=fftest
+SLIBS = -ldsync
+SOURCE = fftest.cc
+include $(PROGRAM_H)
+
+# Program to test the File Filter
+PROGRAM=pathtest
+SLIBS = -ldsync
+SOURCE = pathtest.cc
+include $(PROGRAM_H)
diff --git a/tools/dsync-0.0/test/pathtest.cc b/tools/dsync-0.0/test/pathtest.cc
new file mode 100644 (file)
index 0000000..e95c794
--- /dev/null
@@ -0,0 +1,221 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <dsync/error.h>
+#include <iostream>
+
+// SimplifyPath - Short function to remove relative path components    /*{{{*/
+// ---------------------------------------------------------------------
+/* This short function removes relative path components such as ./ and ../
+   from the path and removes double // as well. It works by seperating
+   the path into a list of components and then removing any un-needed
+   compoments */
+bool SimplifyPath(char *Buffer)
+{
+   // Create a list of path compoments
+   char *Pos[100];
+   unsigned CurPos = 0;
+   Pos[CurPos] = Buffer;
+   CurPos++;   
+   for (char *I = Buffer; *I != 0;)
+   {
+      if (*I == '/')
+      {
+        *I = 0;
+        I++;
+        Pos[CurPos] = I;
+        CurPos++;
+      }
+      else
+        I++;
+   }
+   
+   // Strip //, ./ and ../
+   for (unsigned I = 0; I != CurPos; I++)
+   {
+      if (Pos[I] == 0)
+        continue;
+      
+      // Double slash
+      if (Pos[I][0] == 0)
+      {
+        if (I != 0)
+           Pos[I] = 0;
+        continue;
+      }
+      
+      // Dot slash
+      if (Pos[I][0] == '.' && Pos[I][1] == 0)
+      {
+        Pos[I] = 0;
+        continue;
+      }
+      
+      // Dot dot slash
+      if (Pos[I][0] == '.' && Pos[I][1] == '.' && Pos[I][2] == 0)
+      {
+        Pos[I] = 0;
+        unsigned J = I;
+        for (; Pos[J] == 0 && J != 0; J--);
+        if (Pos[J] == 0)
+           return _error->Error("Invalid path, too many ../s");
+        Pos[J] = 0;     
+        continue;
+      }
+   }  
+
+   // Recombine the path into full path
+   for (unsigned I = 0; I != CurPos; I++)
+   {
+      if (Pos[I] == 0)
+        continue;
+      memmove(Buffer,Pos[I],strlen(Pos[I]));
+      Buffer += strlen(Pos[I]);
+      
+      if (I + 1 != CurPos)
+        *Buffer++ = '/';
+   }                   
+   *Buffer = 0;
+   
+   return true;
+}
+                                                                       /*}}}*/
+// ResolveLink - Resolve a file into an unsymlinked path               /*{{{*/
+// ---------------------------------------------------------------------
+/* The returned path is a path that accesses the same file without 
+   traversing a symlink, the memory buffer used should be twice as large
+   as the largest path. It uses an LRU cache of past lookups to speed things
+   up, just don't change directores :> */
+struct Cache
+{
+   string Dir;
+   string Trans;
+   unsigned long Age;
+};
+static Cache DirCache[400];
+static unsigned long CacheAge = 0;
+bool ResolveLink(char *Buffer,unsigned long Max)
+{
+   if (Buffer[0] == 0 || (Buffer[0] == '/' && Buffer[1] == 0))
+      return true;
+   // Lookup in the cache
+   Cache *Entry = 0;
+   for (int I = 0; I != 400; I++)
+   {
+      // Store an empty entry
+      if (DirCache[I].Dir.empty() == true)
+      {
+        Entry = &DirCache[I];
+        Entry->Age = 0;
+        continue;
+      }
+      
+      // Store the LRU entry
+      if (Entry != 0 && Entry->Age > DirCache[I].Age)
+        Entry = &DirCache[I];
+      
+      if (DirCache[I].Dir != Buffer || DirCache[I].Trans.empty() == true)
+        continue;
+      strcpy(Buffer,DirCache[I].Trans.c_str());
+      DirCache[I].Age = CacheAge++;
+      return true;
+   }
+   
+   // Prepare the cache for our new entry
+   if (Entry != 0 && Buffer[strlen(Buffer) - 1] == '/')
+   {
+      Entry->Age = CacheAge++;
+      Entry->Dir = Buffer;
+   }   
+   else
+      Entry = 0;
+
+   // Resolve any symlinks
+   unsigned Counter = 0;
+   while (1)
+   {
+      Counter++;
+      if (Counter > 50)
+        return _error->Error("Exceeded allowed symlink depth");
+      
+      // Strip off the final component name
+      char *I = Buffer + strlen(Buffer);
+      for (; I != Buffer && (*I == '/' || *I == 0); I--);
+      for (; I != Buffer && *I != '/'; I--);
+      if (I != Buffer)
+        I++;
+
+      // If it is a link then read the link dest over the final component
+      int Res = readlink(Buffer,I,Max - (I - Buffer));
+      if (Res > 0)
+      {
+        I[Res] = 0;
+        
+        // Absolute path..
+        if (*I == '/')
+           memmove(Buffer,I,strlen(I)+1);
+
+        if (SimplifyPath(Buffer) == false)
+           return false;
+      }
+      else
+        break;
+   }
+   
+   /* Here we are abusive and move the current path component to the end 
+      of the buffer to advoid allocating space */
+   char *I = Buffer + strlen(Buffer);
+   for (; I != Buffer && (*I == '/' || *I == 0); I--);
+   for (; I != Buffer && *I != '/'; I--);
+   if (I != Buffer)
+      I++;
+   unsigned Len = strlen(I) + 1;
+   char *End = Buffer + Max - Len;
+   memmove(End,I,Len);
+   *I = 0;
+
+   // Recurse to deal with any links in the files path
+   if (ResolveLink(Buffer,Max - Len) == false)
+      return false;
+   I = Buffer + strlen(Buffer);
+   memmove(I,End,Len);
+
+   // Store in the cache
+   if (Entry != 0)
+      Entry->Trans = Buffer;
+   
+   return true;
+}
+                                                                       /*}}}*/
+
+int main(int argc,char *argv[])
+{
+   char Buf[1024*4];
+//   strcpy(Buf,argv[1]);
+   while (!cin == false)
+   {
+      char Buf2[200];
+      cin.getline(Buf2,sizeof(Buf2));
+      strcpy(Buf,Buf2);
+      
+      if (ResolveLink(Buf,sizeof(Buf)) == false)
+        _error->DumpErrors();
+      else
+      {
+/*      struct stat StA;
+        struct stat StB;
+        if (stat(Buf,&StA) != 0 || stat(Buf2,&StB) != 0)
+        {
+           cerr << Buf << ',' << Buf2 << endl;
+           cerr << "Stat failure" << endl;
+        }
+        
+        if (StA.st_ino != StB.st_ino)
+           cerr << "Inode mismatch" << endl;*/
+        
+        cout << Buf << endl;
+      }      
+   }
+   return 0;
+}
index ce72d65afd5e4fbaa2bba9d71ad8c7a7687c021b..4c1b45866cde3a5832a871c41bd734d81a85fbcf 100755 (executable)
@@ -17,8 +17,8 @@ import PyRSS2Gen
 
 from debian_bundle.deb822 import Changes
 
-inrss_filename = "changes_in.rss"
-outrss_filename = "changes_out.rss"
+inrss_filename = "NEW_in.rss"
+outrss_filename = "NEW_out.rss"
 db_filename = "status.db"
 
 parser = OptionParser()
diff --git a/web/dinstall.html b/web/dinstall.html
new file mode 100644 (file)
index 0000000..2d0f967
--- /dev/null
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML>
+<HEAD>
+<TITLE>Dinstall might be running in...</TITLE>
+
+</HEAD>
+
+
+<BODY>
+
+
+<h1>Dinstall might be running (more or less) in...</h1>
+
+<br>
+
+<script type="text/javascript">
+document.write('<table><tr><td id="time" style="border:3px ridge black;padding:2px;color:white;background-color:#de0452;font-size:40px"> </td></tr></table>');
+dinstall = new Date();
+dinstall.setUTCHours(19);
+dinstall.setUTCMinutes(52);
+dinstall.setUTCSeconds(00);
+
+function countdown()
+{
+    var now = new Date();
+    var rest = Math.floor((dinstall - now)/1000) % 43200;
+    if(now>dinstall) rest+=43200;
+    
+    stunden = Math.floor(rest/3600);
+    if (stunden < 10) stunden = "0" + stunden;
+    rest %= 3600;
+
+    minuten = Math.floor(rest/60);
+    if (minuten < 10) minuten = "0" + minuten;
+    rest %= 60;
+
+    sekunden = rest;
+    if (sekunden < 10) sekunden = "0" + sekunden;
+
+    document.getElementById('time').firstChild.data=stunden+':'+minuten+':'+sekunden;
+}
+
+setInterval('countdown()',1000);
+</script><br>
+
+<br><br>
+
+dinstall should run 07h 52min and 19h 52min or 07h 52min AM/PM (UTC)
+
+<br><br>
+<font size="-2">Made by Eduard Bloch &lt;blade@debian.org&gt;
+<br>Small update to use 12h dinstall by Felipe Augusto van de Wiel (faw)
+<br>Please check this <a href="http://lists.debian.org/debian-cd/2006/11/msg00049.html">announcement</a> about dinstall twice daily.
+
+</BODY>
+
+</HTML>
+
+<!-- (C) 2005 by Eduard Block <blade@debian.org> -->
diff --git a/web/style.css b/web/style.css
new file mode 100644 (file)
index 0000000..b942d1b
--- /dev/null
@@ -0,0 +1,302 @@
+p.note {
+ font-family: sans-serif;
+ color: #900;
+ text-align: center;
+ padding: 5px;
+ font-size: 11px;
+ font-weight: normal;
+}
+
+div.c1 {text-align: center}
+
+p.text {
+ font-family: sans-serif;
+ padding: 5px;
+ font-size: 105%;
+ font-weight: normal;
+}
+
+body {
+ font-family: Arial, Helvetica, sans-serif;
+ color: #000000;
+ background-color: #FFF;
+}
+
+table.reddy
+{
+ color: #000;
+ background-color: #000;
+ border: 0px solid #000;
+ border-collapse: collapse;
+}
+
+ul {
+ color: #222;
+ font-size: 115%;
+}
+
+td.reddy {
+ font-family: serif;
+ font-size: 24px;
+ font-weight: normal;
+ background-color: #DF0451;
+ color: #FFFF00;
+ border: 0px solid #DF0451;
+ vertical-align: middle;
+ text-align: center;
+ padding: 0px;
+}
+
+p.validate {
+ text-align: center;
+}
+
+table
+{
+ color: #000;
+ background-color: #000;
+ border: 0px solid #000;
+ border-collapse: separate;
+ border-spacing: 1px;
+}
+
+h1 {
+ font-size: 200%;
+ text-align: center;
+ color: #000;
+}
+
+h2 {
+ font-size: 160%;
+ color: #000;
+}
+
+p {
+ font-size: 100%;
+ font-weight: bold;
+ text-align: justify;
+ color: #222;
+}
+
+tr {
+ background-color: #FFF;
+}
+
+tr.odd {
+ background-color: #FFFFFF;
+}
+
+tr.even {
+ background-color: #e8e8e8;
+}
+
+td.sid {
+ color: #000;
+ text-align: left;
+}
+
+tr.experimental {
+ color: #cc0000;
+}
+
+tr.unstable {
+ color: #345677;
+}
+
+tr.sid_odd {
+ color: #000;
+}
+
+td.exp {
+ color: #cc0000;
+ text-align: left;
+}
+
+tr.exp_odd {
+ color: #900;
+}
+
+th {
+ font-size: 120%;
+ text-align: center;
+ font-weight: bold;
+ background-color: #BDF;
+ border: 0px solid #000;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ padding-left: 6px;
+ padding-right: 6px;
+}
+
+th.reject {
+ font-size: 120%;
+ text-align: center;
+ font-weight: bold;
+ background-color: #BDF;
+ border: 0px solid #000;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ padding-left: 6px;
+ padding-right: 6px;
+}
+
+td {
+ font-size: 105%;
+ border: 0px solid #000;
+ padding: 4px;
+ padding-left: 6px;
+ padding-right: 6px;
+}
+
+a:link {
+ color: #0000FF;
+ text-decoration: none;
+}
+
+a:visited {
+ color: #800080;
+ text-decoration: none;
+}
+
+a:active {
+ color: #FF0000;
+ text-decoration: none;
+}
+
+a:hover{
+ color: #0000FF;
+ text-decoration: underline;
+}
+
+.footer {
+ font-size: 90%;
+}
+
+/************* NEW details pages ************/
+
+/* this overides some overly general styles above */
+#NEW-details-page th {
+ background-color: white;
+ padding: 0;
+}
+#NEW-details-page table { 
+ background-color: white;
+ padding: 0;
+}
+
+#NEW-details-page a img {
+       text-decoration: none;
+       border: none;
+}
+#NEW-details-page #logo {
+       text-align: center;
+       margin-bottom: 1.5em;
+}
+#NEW-details-page #titleblock {
+       position: relative;
+       width: 100%;
+       text-align: center;
+       background-color: #DF0451;
+       color: #FFFF00;
+       padding: 0.2em;
+       margin-bottom: 1.5em;
+}
+#NEW-details-page #titleblock a {
+        color: #FFFF00;
+        text-decoration: none;
+}
+#NEW-details-page #titleblock a:hover {
+       color: #FFFF00;
+       text-decoration: underline;
+}
+#NEW-details-page #menu a:visited {
+       color: #0000FF;
+       text-decoration: none;
+}
+#NEW-details-page #menu a:hover {
+       color: #0000FF;
+       text-decoration: none;
+}
+#NEW-details-page #red-upperleft {
+       position: absolute;
+       top: 0px;
+       left: 0px;
+}
+#NEW-details-page #red-lowerleft {
+       position: absolute;
+       bottom: 0px;
+       left: 0px;
+}
+#NEW-details-page #red-upperright {
+       position: absolute;
+       top: 0px;
+       right: 0px;
+}
+#NEW-details-page #red-lowerright {
+       position: absolute;
+       bottom: 0px;
+       right: 0px;
+}
+#NEW-details-page #titleblock .title {
+       vertical-align: middle;
+        font-weight: normal;
+       font-family: serif;
+       font-size: 22px;
+}
+#NEW-details-page .toggle-msg { 
+        font-weight: normal;
+       font-family: serif;
+       font-size: 80%;
+}
+#NEW-details-page #menu {
+       font-size: 80%;
+       position: fixed;
+       bottom: 1em;
+       right: 0em;
+       background: #ddd;
+       width: 9%;
+       padding: 0.5em 0 0.5em 0.5em;
+}
+#NEW-details-page #menu p {
+       margin: 0px;
+       font-size: 80%;
+}
+#NEW-details-page .infobox {
+       width: 80%;
+       border: solid black 2px;
+       margin: 1em auto;
+}
+#NEW-details-page #menu .title,.infobox .title {
+       text-align: center;
+       font-weight: bold;
+       border-bottom: dashed 1px black;
+       margin: 0.2em;
+}
+#NEW-details-page .infobox .subtitle { 
+       text-align: center;
+       font-weight: bold;
+       border-bottom: dashed 1px black;
+       margin: 0.2em;
+}
+#NEW-details-page table.infobox {
+       padding: 0 .5em 1em .5em;
+}
+#NEW-details-page div.infobody {
+       margin: .5em 1em;
+}
+#NEW-details-page .rfc822 .key,#NEW-details-page .val {
+       font-family: monospace;
+}
+#NEW-details-page .rfc822 .key {
+       vertical-align: baseline;
+}
+#NEW-details-page .rfc822 .val {
+}
+#NEW-details-page .copyright .infobody {
+       font-family: monospace;
+       white-space: pre;
+}
+
+#NEW-details-page td pre { 
+        margin: 0pt
+}