chiark / gitweb /
chiark-utils (4.2.0) unstable; urgency=low
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 10 Jun 2012 20:40:32 +0000 (21:40 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 10 Jun 2012 20:40:32 +0000 (21:40 +0100)
  * Rename `xacpi-simple' to `xbatmon-simple':
    - rename source file
    - change program name in Makefiles, .gitignore, rules, etc.
    - change program's idea of its own name for usage message
    - look for X resources under both old ane new names
    - provide a compatibility symlink (using dh_link)

# imported from the archive

136 files changed:
COPYING [new file with mode: 0644]
TODO [new file with mode: 0644]
backup/Makefile [new file with mode: 0644]
backup/backuplib.pl [new file with mode: 0644]
backup/bringup [new file with mode: 0755]
backup/checkallused [new file with mode: 0755]
backup/driver [new file with mode: 0755]
backup/examples/chiark/SYMLINKS.tar [new file with mode: 0644]
backup/examples/chiark/expected-diffs [new file with mode: 0644]
backup/examples/chiark/fsys.all [new file with mode: 0644]
backup/examples/chiark/fsys.pt0 [new file with mode: 0644]
backup/examples/chiark/fsys.pt1 [new file with mode: 0644]
backup/examples/chiark/settings.pl [new file with mode: 0644]
backup/examples/chiark/settings.sh [new file with mode: 0755]
backup/examples/chiark/tape.a0 [new file with mode: 0644]
backup/examples/chiark/tape.b0 [new file with mode: 0644]
backup/examples/chiark/tape.b1 [new file with mode: 0644]
backup/examples/chiark/tape.c0 [new file with mode: 0644]
backup/examples/chiark/tape.c1 [new file with mode: 0644]
backup/examples/chiark/tape.d0 [new file with mode: 0644]
backup/examples/chiark/tape.d1 [new file with mode: 0644]
backup/examples/chiark/tape.ka [new file with mode: 0644]
backup/examples/chiark/tape.kb [new file with mode: 0644]
backup/examples/chiark/warnings.now [new file with mode: 0755]
backup/examples/chiark/warnings.soon [new file with mode: 0755]
backup/examples/relativity/bringup-hook [new file with mode: 0755]
backup/examples/relativity/expected-diffs [new file with mode: 0644]
backup/examples/relativity/fsys.all [new file with mode: 0644]
backup/examples/relativity/fsys.pt0 [new file with mode: 0644]
backup/examples/relativity/fsys.pt1 [new file with mode: 0644]
backup/examples/relativity/fsys.pt2 [new file with mode: 0644]
backup/examples/relativity/ifsys.prefixes [new file with mode: 0644]
backup/examples/relativity/ifsys.pt0 [new file with mode: 0644]
backup/examples/relativity/ifsys.pt1 [new file with mode: 0644]
backup/examples/relativity/ifsys.pt2 [new file with mode: 0644]
backup/examples/relativity/settings.pl [new file with mode: 0644]
backup/examples/relativity/tape.ks [new file with mode: 0644]
backup/examples/relativity/tape.kt [new file with mode: 0644]
backup/examples/relativity/tape.s0 [new file with mode: 0644]
backup/examples/relativity/tape.s1 [new file with mode: 0644]
backup/examples/relativity/tape.s2 [new file with mode: 0644]
backup/examples/relativity/tape.t0 [new file with mode: 0644]
backup/examples/relativity/tape.t1 [new file with mode: 0644]
backup/examples/relativity/tape.t2 [new file with mode: 0644]
backup/examples/relativity/tape.u0 [new file with mode: 0644]
backup/examples/relativity/tape.u1 [new file with mode: 0644]
backup/examples/relativity/tape.u2 [new file with mode: 0644]
backup/examples/relativity/tape.v0 [new file with mode: 0644]
backup/examples/relativity/tape.v1 [new file with mode: 0644]
backup/examples/relativity/tape.v2 [new file with mode: 0644]
backup/examples/relativity/warnings.now [new file with mode: 0755]
backup/examples/relativity/warnings.soon [new file with mode: 0755]
backup/full [new file with mode: 0755]
backup/increm [new file with mode: 0755]
backup/iwjbackup.txt [new file with mode: 0644]
backup/labeltape [new file with mode: 0755]
backup/loaded [new file with mode: 0755]
backup/lvm [new file with mode: 0755]
backup/man/checkallused.1 [new file with mode: 0644]
backup/man/driver.1 [new file with mode: 0644]
backup/man/labeltape.1 [new file with mode: 0644]
backup/man/loaded.1 [new file with mode: 0644]
backup/man/takedown.1 [new file with mode: 0644]
backup/man/whatsthis.1 [new file with mode: 0644]
backup/nosnap [new file with mode: 0755]
backup/remount [new file with mode: 0755]
backup/remountrocp [new file with mode: 0755]
backup/snap-common [new file with mode: 0644]
backup/snap-drop [new file with mode: 0755]
backup/snaprsync [new file with mode: 0755]
backup/takedown [new file with mode: 0755]
backup/whatsthis [new file with mode: 0755]
cprogs/Makefile [new file with mode: 0644]
cprogs/dlist.h [new file with mode: 0644]
cprogs/mcastsoundd.c [new file with mode: 0644]
cprogs/myopt.c [new file with mode: 0644]
cprogs/myopt.h [new file with mode: 0644]
cprogs/rcopy-repeatedly.c [new file with mode: 0644]
cprogs/readbuffer.1 [new file with mode: 0644]
cprogs/readbuffer.c [new file with mode: 0644]
cprogs/really.8 [new file with mode: 0644]
cprogs/really.c [new file with mode: 0644]
cprogs/really.testcases [new file with mode: 0755]
cprogs/rwbuffer.c [new file with mode: 0644]
cprogs/rwbuffer.h [new file with mode: 0644]
cprogs/smtpallow.c [new file with mode: 0644]
cprogs/summer.1 [new file with mode: 0644]
cprogs/summer.c [new file with mode: 0644]
cprogs/trivsoundd-start [new file with mode: 0755]
cprogs/trivsoundd.8 [new file with mode: 0644]
cprogs/trivsoundd.c [new file with mode: 0644]
cprogs/usernice.c [new file with mode: 0644]
cprogs/usr-local-src-misc-Makefile [new file with mode: 0644]
cprogs/watershed.c [new file with mode: 0644]
cprogs/with-lock-ex.1 [new file with mode: 0644]
cprogs/with-lock-ex.c [new file with mode: 0644]
cprogs/wrbufcore.c [new file with mode: 0644]
cprogs/writebuffer.1 [new file with mode: 0644]
cprogs/writebuffer.c [new file with mode: 0644]
cprogs/xbatmon-simple.c [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/chiark-backup/conffiles [new file with mode: 0644]
debian/chiark-utils-bin.links [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
scripts/ChiarkNNTP.pm [new file with mode: 0644]
scripts/Makefile [new file with mode: 0644]
scripts/cvs-adjustroot [new file with mode: 0755]
scripts/cvs-repomove [new file with mode: 0755]
scripts/cvsweb-list [new file with mode: 0755]
scripts/expire-iso8601 [new file with mode: 0755]
scripts/genspic2gnuplot [new file with mode: 0755]
scripts/gnucap2genspic [new file with mode: 0755]
scripts/hexterm [new file with mode: 0755]
scripts/named-conf [new file with mode: 0755]
scripts/named-conf.8 [new file with mode: 0644]
scripts/ngspice2genspic [new file with mode: 0755]
scripts/nntpid [new file with mode: 0755]
scripts/palm-datebook-reminders [new file with mode: 0755]
scripts/palm-datebook-reminders.1 [new file with mode: 0644]
scripts/random-word [new file with mode: 0755]
scripts/remountresizereiserfs [new file with mode: 0755]
scripts/summarise-mailbox-preserving-privacy [new file with mode: 0755]
settings.make [new file with mode: 0644]
sync-accounts/Makefile [new file with mode: 0644]
sync-accounts/grab-account [new file with mode: 0755]
sync-accounts/grab-account.8 [new file with mode: 0644]
sync-accounts/sync-accounts [new file with mode: 0755]
sync-accounts/sync-accounts-createuser [new file with mode: 0755]
sync-accounts/sync-accounts-createuser.8 [new file with mode: 0644]
sync-accounts/sync-accounts.5 [new file with mode: 0644]
sync-accounts/sync-accounts.8 [new file with mode: 0644]
sync-accounts/sync-accounts.example-bsd [new file with mode: 0644]
sync-accounts/sync-accounts.example-linux [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..4432540
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,676 @@
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  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
+them 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 prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  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.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                      TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+  
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  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.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                    END OF TERMS AND CONDITIONS
+
+           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
+state 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) <year>  <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 3 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, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program 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, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU 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 Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..e67e14d
--- /dev/null
+++ b/TODO
@@ -0,0 +1,17 @@
+BACKUP
+======
+
+would be nice someday
+---------------------
+increms after fulls
+replace loaded with idformat
+read/writebuffer setuid --mlock
+whatsthis no cloneandhack
+increm no cloneandhack
+whatsthis listing for zafio and dump archives
+configuration files autogenerator
+
+
+SCRIPTS
+=======
+manpage for gnucap2gnuplot
diff --git a/backup/Makefile b/backup/Makefile
new file mode 100644 (file)
index 0000000..9ca10ba
--- /dev/null
@@ -0,0 +1,70 @@
+# Makefile
+# simple make settings
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+us=    chiark-backup
+
+include ../settings.make
+
+BINSCRIPTS=    checkallused loaded driver takedown whatsthis labeltape \
+               snaprsync
+SHARESCRIPTS=  bringup full increm snap-drop
+SHAREFILES=    backuplib.pl snap-common
+SNAPKINDS=     lvm remount remountrocp nosnap
+
+EXAMPLES=      relativity chiark
+
+all:
+
+install:               all
+               $(INSTALL_DIRECTORY) $(confdir) $(confdir)/snap $(bindir) \
+                       $(sharedir) $(vardir) $(man1dir)
+               set -e; for s in $(BINSCRIPTS); do \
+                       $(INSTALL_SCRIPT) $$s $(bindir)/backup-$$s; done
+               $(INSTALL_SHARE) $(SHAREFILES) $(sharedir)
+               $(INSTALL_SCRIPT) $(SHARESCRIPTS) $(sharedir)
+               set -e; for s in $(SNAPKINDS); do \
+                       d=$(confdir)/snap/$$s; \
+                       test ! -f $$d || d=$$d.dist; \
+                       $(INSTALL_SCRIPT) $$s $$d; done
+
+install-docs:
+               $(INSTALL_DIRECTORY) $(txtdocdir)
+               $(INSTALL_SHARE) iwjbackup.txt $(txtdocdir)/README
+
+install-examples:
+               set -e; for e in $(EXAMPLES); do \
+                       cd examples/$$e; \
+                       $(INSTALL_DIRECTORY) $(exampledir)/$$e; \
+                       $(INSTALL_SHARE) [!A-Z]*[!~] $(exampledir)/$$e; \
+                       if test -f SYMLINKS.tar; then \
+                               exec <SYMLINKS.tar; \
+                               (set -e; cd $(exampledir)/$$e && tar -xf -); \
+                       fi; \
+                       cd ../..; \
+               done
+
+clean:
+               rm -f *~ ./#*# *.o
+
+distclean realclean:   clean
diff --git a/backup/backuplib.pl b/backup/backuplib.pl
new file mode 100644 (file)
index 0000000..cbbe6f4
--- /dev/null
@@ -0,0 +1,285 @@
+# backuplib.pl
+# core common routines
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+require IO::File;
+
+$nice='nice ' if !defined $nice;
+
+sub printdate () {
+    print scalar(localtime),"\n";
+}
+
+# Set status info -- we write the current status to a file 
+# so if we hang or crash the last thing written to the file
+# will tell us where we were when things went pear-shaped.
+sub setstatus ($) {
+    open S, ">this-status.new" or die $!;
+    print S $_[0],"\n" or die $!;
+    close S or die $!;
+    rename "this-status.new","this-status" or die $!;
+}
+
+# startprocess, endprocesses, killprocesses are 
+# used to implement the funky pipeline stuff.
+sub startprocess ($$$) {
+    my ($i,$o,$c) = @_;
+    pboth("  $c\n");
+    defined($p= fork) or die $!;
+    if ($p) { $processes{$p}= $c; return; }
+    open STDIN,"$i" or die "$c stdin $i: $!";
+    open STDOUT,"$o" or die "$c stdout $o: $!";
+    &closepipes;
+    exec $c; die "$c: $!";
+}
+
+sub rewind_raw () {
+    runsystem("mt -f $tape rewind");
+}
+
+sub readtapeid_raw () {
+    open T, ">>TAPEID" or die $!; close T;
+    unlink 'TAPEID' or die $!;
+    rewind_raw();
+    system "mt -f $tape setblk $blocksizebytes"; $? and die $?;
+    system "dd if=$tape bs=${blocksize}b count=10 ".
+          "| tar -b$blocksize -vvxf - TAPEID";
+}
+
+sub runsystem ($) {
+    pboth("    $_[0]\n");
+    system $_[0];
+    $? and die $?;
+}
+
+sub pboth ($) {
+    my ($str) = @_;
+    print LOG $str or die $!;
+    print $str or die $!;
+}
+
+sub nexttapefile ($) {
+    my ($what) = @_;
+    $currenttapefilenumber++;
+    $currenttapefilename= $what;
+    pboth(sprintf "writing tape file #%d (mt fsf %d): %s\n",
+         $currenttapefilenumber, $currenttapefilenumber-1, $what);
+}
+
+sub writetapeid ($$) {
+    open T, ">TAPEID" or die $!;
+    print T "$_[0]\n$_[1]\n" or die $!;
+    close T or die $!;
+
+    $currenttapefilenumber= 0;
+    nexttapefile('TAPEID');
+
+    system "tar -b$blocksize -vvcf TAPEID.tar TAPEID"; $? and die $?;
+    system "dd if=TAPEID.tar of=$ntape bs=${blocksize}b count=10";
+    $? and die $?;
+}
+
+sub endprocesses () {
+    while (keys %processes) {
+       $p= waitpid(-1,0) or die "wait: $!";
+       if (!exists $processes{$p}) { warn "unknown pid exited: $p, code $?\n"; next; }
+       $c= $processes{$p};
+       delete $processes{$p};
+       $? && die "error: command gave code $?: $c\n";
+    }
+    pboth("  ok\n");
+}
+
+sub killprocesses {
+    for $p (keys %processes) {
+       kill 15,$p or warn "kill process $p: $!";
+    }
+    undef %processes;
+}
+
+# Read a fsys.foo filesystem group definition file.
+# Syntax is: empty lines and those beginning with '#' are ignored.
+# Trailing whitespace is ignored. Lines of the form 'prefix foo bar'
+# are handled specially, as arex lines 'exclude regexp'; otherwise 
+# we just shove the line into @fsys and let parsefsys deal with it.
+
+sub readfsysfile ($) {
+    my ($fn) = @_;
+    my ($fh,$sfn);
+    $fh= new IO::File "$fn", "r" or die "cannot open fsys file $fn ($!).\n";
+    for (;;) {
+       $!=0; $_= <$fh> or die "unexpected EOF in $fn ($!)\n";
+       chomp; s/\s*$//;
+       last if m/^end$/;
+       next unless m/\S/;
+       next if m/^\#/;
+       if (m/^prefix\s+(\w+)\s+(\S.*\S)$/) {
+           $prefix{$1}= $2;
+       } elsif (m/^prefix\-df\s+(\w+)\s+(\S.*\S)$/) {
+           $prefixdf{$1}= $2;
+       } elsif (m/^snap(?:\=(\w+))?\s+(\w+)\s+(\w+)$/) {
+            push @excldir,$1;
+       } elsif (m/^excludedir\s+(\S.*\S)$/) {
+            push @excldir,$1;
+        } elsif (m/^exclude\s+(\S.*\S)$/) {
+            push @excl,$1;
+       } elsif (m/^include\s+(\S.*\S)$/) {
+           $sfn = $1;
+           $sfn =~ s/^\./fsys./;
+           $sfn = "$etc/$sfn" unless $sfn =~ m,^/,;
+           readfsysfile($sfn);
+        } else {
+           push @fsys,$_;
+       }
+    }
+    close $fh or die $!;
+}
+
+sub readfsys ($) {
+    my ($fsnm) = @_;
+    my ($fsf);
+    $fsf= "$etc/fsys.$fsnm";
+    stat $fsf or die "Filesystems $fsnm unknown ($!).\n";
+    readfsysfile($fsf);
+}
+
+# Parse a line from a filesystem definition file. We expect the line
+# to be in $tf.
+sub parsefsys () {
+    my ($dopts,$dopt);
+    if ($tf =~ m#^(/\S*)\s+(\w+)([,=0-9a-z]*)$#) {
+        # Line of form '[/device:]/file/system dumptype[,options]'
+       $atf= $1;
+       $tm= $2;
+       $dopts= $3;
+       $prefix= '<local>';
+       $pcstr= '';
+       $rstr= '';
+    } elsif ($tf =~ m#^(/\S*)\s+(\w+)([,=0-9a-z]*)\s+(\w+)$#) {
+        # Line of form '[/device:]/file/system dumptype[,options] prefix'
+        # (used for remote backups)
+       $atf= $1;
+       $tm= $2;
+       $dopts= $3;
+       $prefix= $4;
+       $pcstr= "$prefix:";
+       defined($prefix{$prefix}) or die "prefix $prefix in $tf ?\n";
+       $rstr= $prefix{$prefix}.' ';
+    } else {
+       die "fsys $tf ?";
+    }
+
+    $fsidstr= $pcstr.$atf;
+    $fsidstr =~ s/[,+]/+$&/g;
+    $fsidstr =~ s#/#,#g;
+    $fsidfile= "/var/lib/chiark-backup/incstamp,$fsidstr";
+
+    $dev = $atf =~ s,^(.*)\:,, ? $1 : '';
+
+    if (!length $pcstr) {
+       stat $atf or die "stat $atf: $!";
+       -d _ or die "not a dir: $atf";
+    }
+
+    undef %dopt;
+    foreach $dopt (split /\,/,$dopts) {
+       if (grep { $dopt eq $_ } qw(gz noinc)) {
+           $dopt{$dopt}= 'y';
+       } elsif (grep { $dopt eq $_ } qw(snap)) {
+           $dopt{$dopt}= $dopt;
+       } elsif ($dopt =~ m/\=/ && grep { $` eq $_ } qw(gz snap)) {
+           $dopt{$`}= $';
+       } elsif (length $dopt) {
+           die "unknown option $dopt (in $dopts $tf)";
+       }
+    }
+
+    my ($gzo);
+    foreach $gzo (qw(gz gzi)) {
+       if ($dopt{$gzo} eq 'y') {
+           $$gzo= '1';
+       } elsif ($dopt{$gzo} =~ m/^\d$/) {
+           $$gzo= $dopt{$gzo};
+       } elsif (defined $dopt{$gzo}) {
+           die "$tf bad $gzo";
+       } else {
+           $$gzo= '';
+       }
+    }
+
+    if (length $dopt{'snap'}) {
+       length $dev or die "$pcstr:$atf no device but needed for snap";
+    }
+}
+
+sub execute ($) {
+    pboth("  $_[0]\n");
+    system $_[0]; $? and die "$_[0] $?";
+}
+
+sub prepfsys () {
+    $dev_print= $dev;
+    $atf_print= $atf;
+    
+    if (length $dopt{'snap'}) {
+       
+       system('snap-drop'); $? and die $?;
+       
+       $snapscripts= '/etc/chiark-backup/snap';
+       $snapbase= "$rstr $snapscripts/$dopt{'snap'}";
+       $snapargs= "/var/lib/chiark-backup";
+
+       $snapsnap= "$snapbase snap $snapargs $dev $atf";
+       $snapdrop= "$snapbase drop $snapargs";
+
+       open SD, ">snap-drop.new" or die $!;
+       print SD $snapdrop,"\n" or die $!;
+       close SD or die $!;
+       rename "snap-drop.new","snap-drop" or die $!;
+
+       execute($snapsnap);
+
+       $dev_nosnap= $dev;
+       $atf_nosnap= $atf;
+       $dev= "/var/lib/chiark-backup/snap-device";
+       $atf= "/var/lib/chiark-backup/snap-mount";
+    }
+}
+
+sub finfsys () {
+    if (length $dopt{'snap'}) {
+       system('snap-drop'); $? and die $?;
+    }
+}
+
+sub openlog () {
+    unlink 'log';
+    $u= umask(007);
+    open LOG, ">log" or die $!;
+    umask $u;
+    select(LOG); $|=1; select(STDOUT);
+}
+
+$SIG{'__DIE__'}= 'killprocesses';
+
+1;
diff --git a/backup/bringup b/backup/bringup
new file mode 100755 (executable)
index 0000000..b88a720
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+# bringup
+# script for going back to defaultrunlevel
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+# Very simple: extract the default runlevel from /etc/inittab
+# and change to it with telinit.
+
+runlevel=`sed -ne '/^id:/ s/.*:\([0-9]\):.*/\1/p' /etc/inittab`
+telinit $runlevel
+
+test ! -f /etc/chiark-backup/bringup-hook
diff --git a/backup/checkallused b/backup/checkallused
new file mode 100755 (executable)
index 0000000..c0079d0
--- /dev/null
@@ -0,0 +1,182 @@
+#!/usr/bin/perl
+# checkallused
+# check that the configuration is sane and backs up everything it should
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+# All filesystems must either be backed up in both full and
+# incremental dumps or listed as exceptions.
+
+BEGIN {
+    $etc= '/etc/chiark-backup';
+    require "$etc/settings.pl";
+    require 'backuplib.pl';
+}
+
+$|=1;
+
+open X,'last-tape' or die $!;
+chomp($tape= <X>);
+close X or die $!;
+
+while (!defined $tapedone{$tape}) {
+    open X,"$etc/tape.$tape" or die "$tape $!";
+    $fsg=''; $next='';
+    for (;;) {
+       $_= <X> or die $1; chomp; s/\s*$//;
+       last if m/^end$/;
+       next unless m/\S/;
+       next if m/^\#/;
+       if (m/^filesystems (\w+)$/) { $fsg= $1; }
+        elsif (m/^next (\w+)$/) { $next=$1; }
+       else { die "$tape $_ ?"; }
+    }
+    length $fsg or die "$tape $!";
+    length $next or die "$tape $!";
+    push @{$fsgdone{$fsg}}, $tape;
+    $tapedone{$tape}=1;
+    $tape= $next;
+}
+
+sub checkdevspec ($$$) {
+    my ($atf,$devspec,$why) = @_;
+    push @{ $devspec{$atf}{$devspec} }, $why;
+}
+
+for $fsg (sort keys %fsgdone) {
+    print "filesystem group $fsg: ".join(' ',@{$fsgdone{$fsg}}).":\n ";
+    @fsys= ();
+    readfsys($fsg);
+    for $tf (@fsys) {
+       parsefsys();
+       $pstr= "$pcstr$atf";
+       &e("dumped twice ($backed{$pstr}, $fsg): $pstr")
+           if defined $backed{$pstr};
+       $backed{$pstr}= $fsg;
+       checkdevspec($pstr,"$pcstr$dev","filesystem group $fsg")
+           if length $dev;
+       print " $pstr";
+    }
+    print "\n";
+}
+
+print "incremental group:\n ";
+@fsys= ();
+readfsys('all');
+for $tf (@fsys) {
+    parsefsys();
+    $pstr= "$pcstr$atf";
+    $incrd{$pstr}= $fsg;
+    checkdevspec($pstr,"$pcstr$dev","incremental group") if length $dev;
+    print " $pstr";
+}
+print "\n";
+
+for $pfx ('', sort keys %prefix) {
+    $rstr= length($pfx) ? $prefix{$pfx}.' ' : '';
+    $dfstr= exists($prefixdf{$pfx}) ? $prefixdf{$pfx} :
+       'df -P --no-sync -xiso9660 -xnfs -xproc -xtmpfs';
+    $cmd= "$rstr $dfstr";
+    open X, "$cmd |" or die $!;
+    $_= <X>; m/^Filesystem/ or die "$cmd => $_ ?";
+    $prefix= length($pfx) ? $pfx : '<local>';
+    $pcstr= length($pfx) ? "$pfx:" : '';
+    print "mount points: $prefix:";
+    while (<X>) {
+       chomp;
+       next if m,^procfs\s,;
+       m,^/dev/(\S+)\s.*\s(/\S*)\s*$, or die "$_ ?";
+       ($dev,$mp) = ($1,$2);
+       checkdevspec("$pcstr$mp","$pcstr/dev/$dev","df");
+       $mounted{"$pcstr$mp"}="$pcstr$dev"; print " $1-$2";
+       if (defined($backto= $backed{"$pcstr$mp"})) {
+           if (m,^/dev/\S+\s+\d+\s+(\d+)\s,) {
+               $usedkb{$backto} += $1;
+               $countedkb{"$pcstr$mp"}++;
+           }
+       }
+    }
+    print "\n";
+    $!=0; close(X); $? and die "$? $!";
+}
+
+foreach $fsg (keys %usedkb) {
+    print "filesystem group $fsg: $usedkb{$fsg} 1K-blocks raw accounted\n";
+}
+
+foreach $fsg (keys %backed) {
+    next if $countedkb{$fsg};
+    print "unaccounted filesystem: $fsg\n";
+}
+
+foreach $dsk (keys %devspec) {
+    if (keys %{ $devspec{$dsk} } != 1) {
+       foreach $devspec (keys %{ $devspec{$dsk} }) {
+           &e("inconsistent devices for $dsk: $devspec (".
+               join(', ', @{ $devspec{$dsk}{$devspec} }).")");
+       }
+    }
+}
+
+# We check that all mounted filesystems are dumped and all
+# filesystems to be dumped are mounted. The expected-diffs
+# config file allows us to make exceptions.
+# eg: 
+# #expect disk2 to be mounted but not dumped
+# !/disk2
+# # CD may or may not be mounted but should not be dumped in either case
+# ?/cdrom
+
+open Z,"$etc/expected-diffs" or die $!;
+for (;;) {
+    $_= <Z> or die; chomp; s/\s*$//;
+    last if m/^end$/;
+    next unless m/^\S/;
+    next if m/^\#/;
+    if (s/^\?//) {
+        print "non-permanent filesystem expected not to be dumped: $_\n";
+        if (defined($mounted{$_})) {
+            delete $mounted{$_};
+        }
+    } elsif (s/^\!//) {
+       &e("expected not to be dumped, but not a mount point: $_")
+           unless defined($mounted{$_});
+        print "filesystem expected not to be dumped: $_\n";
+        delete $mounted{$_};
+    } else {
+       &e("non-filesystem expected to be dumped is mounted: $_ on $mounted{$_}")
+           if defined($mounted{$_});
+        $mounted{$_}= 'expected-diffs';
+        print "non-filesystem expected to be dumped: $_\n";
+    }
+}
+    
+for $fs (sort keys %backed) { length($mounted{$fs}) || &e("dumped ($backed{$fs}), not a mount point: $fs"); }
+for $fs (sort keys %incrd) { length($mounted{$fs}) || &e("increm'd ($incrd{$fs}), not a mount point: $fs"); }
+for $fs (sort keys %mounted) { length($backed{$fs}) || &e("mount point ($mounted{$fs}), not dumped: $fs"); }
+for $fs (sort keys %mounted) { length($incrd{$fs}) || &e("mount point ($mounted{$fs}), not increm'd: $fs"); }
+
+$emsg.= "configuration ok\n" unless $e;
+print STDERR $emsg;
+exit($e);
+
+sub e { $emsg.="** @_\n"; $e=1; }
diff --git a/backup/driver b/backup/driver
new file mode 100755 (executable)
index 0000000..5dab3eb
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+# driver
+# entry point for inittab (or perhaps cron) to run the backups.
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+cd /var/lib/chiark-backup
+PATH=/usr/share/chiark-backup:$PATH export PATH
+
+if [ "x$1" != test ]; then
+       stty sane
+       stty -isig
+fi
+
+rm -f this-status p p2
+echo 'FAILED to start dump script' >this-status
+
+# Here we go : run 'full', which (name notwithstanding) handles
+# both full and incremental backups, according to the ID of the
+# tape in the drive.
+(full; snap-drop) 2>&1 | tee this-log
+
+status=`cat this-status 2>/dev/null`
+
+# Mail a report to somewhere appropriate; -odq removed (means just 
+# queue message, don't try to deliver) because it just delays the
+# message (you might want that if mail was one of the services turned
+# off for the duration of the backup, though).
+cat <<END - this-log | /usr/lib/sendmail -oi -om -oee -t
+To: dump-reports
+Subject: Dump Report: $status
+
+END
+
+rm -f /TAPEID
+
+if [ "x$1" != test ]; then
+        # Bring the system up as multiuser again
+       bringup
+       stty isig
+fi
diff --git a/backup/examples/chiark/SYMLINKS.tar b/backup/examples/chiark/SYMLINKS.tar
new file mode 100644 (file)
index 0000000..e7db3fe
Binary files /dev/null and b/backup/examples/chiark/SYMLINKS.tar differ
diff --git a/backup/examples/chiark/expected-diffs b/backup/examples/chiark/expected-diffs
new file mode 100644 (file)
index 0000000..644ef8b
--- /dev/null
@@ -0,0 +1,4 @@
+/var/spool/news/chiark
+!/var/spool/news
+!/tmp
+end
diff --git a/backup/examples/chiark/fsys.all b/backup/examples/chiark/fsys.all
new file mode 100644 (file)
index 0000000..3de4e4a
--- /dev/null
@@ -0,0 +1,3 @@
+include fsys.pt0
+include fsys.pt1
+end
diff --git a/backup/examples/chiark/fsys.pt0 b/backup/examples/chiark/fsys.pt0
new file mode 100644 (file)
index 0000000..1412887
--- /dev/null
@@ -0,0 +1,4 @@
+/                      dump
+/var                   dump
+/u                     dump
+end
diff --git a/backup/examples/chiark/fsys.pt1 b/backup/examples/chiark/fsys.pt1
new file mode 100644 (file)
index 0000000..6afe7ef
--- /dev/null
@@ -0,0 +1,4 @@
+/u2                    dump
+/usr                   dump
+/var/spool/news/chiark cpio
+end
diff --git a/backup/examples/chiark/settings.pl b/backup/examples/chiark/settings.pl
new file mode 100644 (file)
index 0000000..2f14cdf
--- /dev/null
@@ -0,0 +1,13 @@
+#
+chdir '/var/lib/chiark-backup' or die $!;
+push(@INC,'/usr/share/chiark-backup');
+$ENV{'PATH'} =~ s,^/usr/share/chiark-backup:,,;
+$ENV{'PATH'}= '/usr/share/chiark-backup:'.$ENV{'PATH'};
+$blocksize= 1;
+$blocksizebytes= 512*$blocksize;
+$softblocksizekb= 1;
+$softblocksizebytes= 1024*$softblocksizekb;
+$tapename= 'st0';
+$tape= "/dev/$tapename";
+$ntape= "/dev/n$tapename";
+1;
diff --git a/backup/examples/chiark/settings.sh b/backup/examples/chiark/settings.sh
new file mode 100755 (executable)
index 0000000..4fad8c5
--- /dev/null
@@ -0,0 +1,10 @@
+# shell script fragment setting options
+# defaults for currently implemented parameters are
+#lvm_vg=
+#lvm_lv=chiark_backup
+#lvm_lvsize_opts=
+# -l <min(no. of free extents in vg, 1.1x total lv size)> (lvm)
+# -l <min(no. of free extents in vg, 1.5x total space used)> (remountrocp)
+#lvm_lvtools_opts='-A n'
+#lvm_lvcreate_opts=
+#lvm_lvcreate_args=
diff --git a/backup/examples/chiark/tape.a0 b/backup/examples/chiark/tape.a0
new file mode 100644 (file)
index 0000000..219e728
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems all
+next b0
+end
diff --git a/backup/examples/chiark/tape.b0 b/backup/examples/chiark/tape.b0
new file mode 100644 (file)
index 0000000..9b0d81f
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems all
+next c0
+end
diff --git a/backup/examples/chiark/tape.b1 b/backup/examples/chiark/tape.b1
new file mode 100644 (file)
index 0000000..0eab3f9
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt1
+next c0
+end
diff --git a/backup/examples/chiark/tape.c0 b/backup/examples/chiark/tape.c0
new file mode 100644 (file)
index 0000000..cb23647
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems all
+next d0
+end
diff --git a/backup/examples/chiark/tape.c1 b/backup/examples/chiark/tape.c1
new file mode 100644 (file)
index 0000000..44f2b50
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt1
+next d0
+end
diff --git a/backup/examples/chiark/tape.d0 b/backup/examples/chiark/tape.d0
new file mode 100644 (file)
index 0000000..6fbdc69
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems all
+next a0
+end
diff --git a/backup/examples/chiark/tape.d1 b/backup/examples/chiark/tape.d1
new file mode 100644 (file)
index 0000000..4856464
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt1
+next a0
+end
diff --git a/backup/examples/chiark/tape.ka b/backup/examples/chiark/tape.ka
new file mode 100644 (file)
index 0000000..cad1fa7
--- /dev/null
@@ -0,0 +1,2 @@
+incremental
+end
diff --git a/backup/examples/chiark/tape.kb b/backup/examples/chiark/tape.kb
new file mode 100644 (file)
index 0000000..cad1fa7
--- /dev/null
@@ -0,0 +1,2 @@
+incremental
+end
diff --git a/backup/examples/chiark/warnings.now b/backup/examples/chiark/warnings.now
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/backup/examples/chiark/warnings.soon b/backup/examples/chiark/warnings.soon
new file mode 100755 (executable)
index 0000000..14d7bd2
--- /dev/null
@@ -0,0 +1,2 @@
+T 45 "in 1 minute"
+T 15 "in 15 seconds"
diff --git a/backup/examples/relativity/bringup-hook b/backup/examples/relativity/bringup-hook
new file mode 100755 (executable)
index 0000000..2d57080
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+set -e
+chvt 11
diff --git a/backup/examples/relativity/expected-diffs b/backup/examples/relativity/expected-diffs
new file mode 100644 (file)
index 0000000..5609903
--- /dev/null
@@ -0,0 +1,7 @@
+#!sfere:/opt/bigdisc
+!davenant:/export/mirror
+davenant:/export/mirror/work
+!davenant:/export/mp3
+!kadath:/tmp
+#!elmyra:/mnt/fat/data
+end
diff --git a/backup/examples/relativity/fsys.all b/backup/examples/relativity/fsys.all
new file mode 100644 (file)
index 0000000..cf611fb
--- /dev/null
@@ -0,0 +1,5 @@
+include ifsys.prefixes
+include        ifsys.pt0
+include ifsys.pt1
+include ifsys.pt2
+end
diff --git a/backup/examples/relativity/fsys.pt0 b/backup/examples/relativity/fsys.pt0
new file mode 100644 (file)
index 0000000..3cd925c
--- /dev/null
@@ -0,0 +1,3 @@
+include ifsys.prefixes
+include ifsys.pt0
+end
diff --git a/backup/examples/relativity/fsys.pt1 b/backup/examples/relativity/fsys.pt1
new file mode 100644 (file)
index 0000000..a9210e7
--- /dev/null
@@ -0,0 +1,3 @@
+include ifsys.prefixes
+include ifsys.pt1
+end
diff --git a/backup/examples/relativity/fsys.pt2 b/backup/examples/relativity/fsys.pt2
new file mode 100644 (file)
index 0000000..2e786f9
--- /dev/null
@@ -0,0 +1,3 @@
+include ifsys.prefixes
+include ifsys.pt2
+end
diff --git a/backup/examples/relativity/ifsys.prefixes b/backup/examples/relativity/ifsys.prefixes
new file mode 100644 (file)
index 0000000..4e317aa
--- /dev/null
@@ -0,0 +1,9 @@
+prefix davenant ssh -o 'BatchMode yes' -c blowfish -o 'Compression yes' davenant 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH'
+prefix xenophobe ssh -o 'BatchMode yes' -c blowfish -o 'Compression no' xenophobe 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH'
+
+prefix khem ssh -o 'BatchMode yes' -c blowfish -o 'Compression yes' -o 'CompressionLevel 1' khem 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH'
+
+prefix kadath ssh -o 'BatchMode yes' -c blowfish -o 'Compression yes' -o 'CompressionLevel 1' kadath -l ian 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH really'
+prefix-df kadath /bin/df -t noprocfs,nfs
+
+end
diff --git a/backup/examples/relativity/ifsys.pt0 b/backup/examples/relativity/ifsys.pt0
new file mode 100644 (file)
index 0000000..2305e5f
--- /dev/null
@@ -0,0 +1,13 @@
+/usr/src               dump    davenant
+
+/var                   dump    davenant
+/u                     dump    davenant
+
+/                      dump    davenant
+
+/export/mirror/work    cpio    davenant
+
+/usr                   dump    davenant
+/boot                  dump    davenant
+
+end
diff --git a/backup/examples/relativity/ifsys.pt1 b/backup/examples/relativity/ifsys.pt1
new file mode 100644 (file)
index 0000000..7ec91b9
--- /dev/null
@@ -0,0 +1,13 @@
+/                      dump    kadath
+
+/usr                   dump    kadath
+/var                   dump    kadath
+/home                  dump    kadath
+
+/var                   dump    khem
+/home                  dump    khem
+/                      dump    khem
+
+/usr                   dump    khem
+
+end
diff --git a/backup/examples/relativity/ifsys.pt2 b/backup/examples/relativity/ifsys.pt2
new file mode 100644 (file)
index 0000000..ac447bb
--- /dev/null
@@ -0,0 +1,8 @@
+/                      dump    xenophobe
+
+/                      dump
+
+/dos/c                 cpio
+/dos/d                 cpio
+
+end
diff --git a/backup/examples/relativity/settings.pl b/backup/examples/relativity/settings.pl
new file mode 100644 (file)
index 0000000..9aa7eb4
--- /dev/null
@@ -0,0 +1,20 @@
+# configuration, for putting in /etc/chiark-backup
+
+chdir '/var/lib/chiark-backup' or die $!;
+push(@INC,'/usr/share/chiark-backup');
+$ENV{'PATH'} =~ s,^/usr/share/chiark-backup,,;
+$ENV{'PATH'}= '/usr/share/chiark-backup:'.$ENV{'PATH'};
+
+# This sets both blocksizes to 512b. Note that both must be the
+# same if using the zftape floppy tape driver, since that requires
+# blocks to be the right size, but dd with the obs=10k option
+# doesn't pad the final block to the blocksize...
+
+$blocksize= 1;
+$blocksizebytes= 512*$blocksize;
+$softblocksizekb= 1;
+$softblocksizebytes= 1024*$softblocksizekb;
+$tapename= 'st0';
+$tape= "/dev/$tapename";
+$ntape= "/dev/n$tapename";
+1;
diff --git a/backup/examples/relativity/tape.ks b/backup/examples/relativity/tape.ks
new file mode 100644 (file)
index 0000000..cad1fa7
--- /dev/null
@@ -0,0 +1,2 @@
+incremental
+end
diff --git a/backup/examples/relativity/tape.kt b/backup/examples/relativity/tape.kt
new file mode 100644 (file)
index 0000000..cad1fa7
--- /dev/null
@@ -0,0 +1,2 @@
+incremental
+end
diff --git a/backup/examples/relativity/tape.s0 b/backup/examples/relativity/tape.s0
new file mode 100644 (file)
index 0000000..a2a30dd
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt0
+next s1
+end
diff --git a/backup/examples/relativity/tape.s1 b/backup/examples/relativity/tape.s1
new file mode 100644 (file)
index 0000000..7d333f5
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt1
+next s2
+end
diff --git a/backup/examples/relativity/tape.s2 b/backup/examples/relativity/tape.s2
new file mode 100644 (file)
index 0000000..81499eb
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt2
+next t0
+end
diff --git a/backup/examples/relativity/tape.t0 b/backup/examples/relativity/tape.t0
new file mode 100644 (file)
index 0000000..3da59fe
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt0
+next t1
+end
diff --git a/backup/examples/relativity/tape.t1 b/backup/examples/relativity/tape.t1
new file mode 100644 (file)
index 0000000..2e5b4f5
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt1
+next t2
+end
diff --git a/backup/examples/relativity/tape.t2 b/backup/examples/relativity/tape.t2
new file mode 100644 (file)
index 0000000..627dbd9
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt2
+next u0
+end
diff --git a/backup/examples/relativity/tape.u0 b/backup/examples/relativity/tape.u0
new file mode 100644 (file)
index 0000000..0c47152
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt0
+next u1
+end
diff --git a/backup/examples/relativity/tape.u1 b/backup/examples/relativity/tape.u1
new file mode 100644 (file)
index 0000000..f570303
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt1
+next u2
+end
diff --git a/backup/examples/relativity/tape.u2 b/backup/examples/relativity/tape.u2
new file mode 100644 (file)
index 0000000..44dcec4
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt2
+next v0
+end
diff --git a/backup/examples/relativity/tape.v0 b/backup/examples/relativity/tape.v0
new file mode 100644 (file)
index 0000000..b9f3ccb
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt0
+next v1
+end
diff --git a/backup/examples/relativity/tape.v1 b/backup/examples/relativity/tape.v1
new file mode 100644 (file)
index 0000000..bfbc0e0
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt1
+next v2
+end
diff --git a/backup/examples/relativity/tape.v2 b/backup/examples/relativity/tape.v2
new file mode 100644 (file)
index 0000000..b85219a
--- /dev/null
@@ -0,0 +1,3 @@
+filesystems pt2
+next s0
+end
diff --git a/backup/examples/relativity/warnings.now b/backup/examples/relativity/warnings.now
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/backup/examples/relativity/warnings.soon b/backup/examples/relativity/warnings.soon
new file mode 100755 (executable)
index 0000000..14d7bd2
--- /dev/null
@@ -0,0 +1,2 @@
+T 45 "in 1 minute"
+T 15 "in 15 seconds"
diff --git a/backup/full b/backup/full
new file mode 100755 (executable)
index 0000000..61b6266
--- /dev/null
@@ -0,0 +1,337 @@
+#!/usr/bin/perl
+# full
+# Main backup script - does a full dump or execs increm.  Do NOT run directly!
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+BEGIN {
+    $etc= '/etc/chiark-backup';
+    require "$etc/settings.pl";
+    require 'backuplib.pl';
+}
+
+$|=1;
+
+while (@ARGV) {
+    $_= shift @ARGV;
+    if (m/^\-\-no\-reten$/) {
+       $noreten=1;
+    } elsif (m/^\-\-no\-config\-check$/) {
+       $nocheck=1;
+    } else {
+       die "unknown option/argument \`$_'\n";
+    }
+}
+
+# Check to see whether the tape.nn and fsys.nn files are sane.
+# checkallused checks that all the filesystems mounted are in fact
+# dumped in both full and incremental dumps.
+
+openlog();
+
+if (!$nocheck) {
+    setstatus "FAILED configuration check";
+    print "Configuration check ...\n" or die $!;
+    system 'backup-checkallused'; $? and die $?;
+} else {
+    setstatus "FAILED rewinding";
+    rewind_raw();
+}
+
+printdate();
+
+setstatus "FAILED reading TAPEID";
+# Try to read the tape ID from the tape into the file TAPEID
+
+readtapeid_raw();
+
+setstatus "FAILED during startup";
+
+# We need some ID; if the tape has one already that takes precedence;
+# otherwise the user might have set a tape ID that this should be
+# by creating really-TAPEID.
+if (open T, "TAPEID") {
+    unlink 'really-TAPEID';
+} elsif (open T, "really-TAPEID") {
+} else {
+    die "No TAPEID.\n";
+}
+
+# read the ID; it had better be a non-empty string of alphanumeric chars.
+chomp($tapeid= <T>);
+$tapeid =~ m/[^0-9a-zA-Z]/ and die "Bad TAPEID ($&).\n";
+$tapeid =~ m/[0-9a-zA-Z]/ or die "Empty TAPEID.\n";
+close T;
+
+setstatus "FAILED at tape identity check";
+
+# We don't let the user overwrite the tape used for the last backup.
+if (open L, "last-tape") {
+    chomp($lasttape= <L>);
+    close L;
+} else {
+    undef $lasttape;
+}
+
+die "Tape $tapeid same as last time.\n" if $tapeid eq $lasttape;
+
+# $tapeid identifies the individual tape; $tapedesc is its current
+# identity and function, for printing in messages.  You can make these
+# namespaces the same if you like, or you can make the tape.<tapeid>
+# files be links to tape.<tapedesc> files.
+if (defined($tapedesc= readlink "$etc/tape.$tapeid")) {
+    $tapedesc =~ s/^.*\.//;
+    $tapedesc .= "($tapeid)";
+} else {
+    $tapedesc = $tapeid;
+}
+
+# Parse the appropriate tape.nn file.
+# Format is: empty lines and lines starting '#' are ignored. Trailing
+# whitespace is ignored. File must end with 'end' on a line by itself.
+# Either there should be a line 'incremental' to indicate that this is
+# a tape for incremental backups, or a pair of lines 'filesystems fsg'
+# and 'next tapeid', indicating that this tape is part of a full 
+# backup, containing the filesystem group fsg. 
+undef $fsys;
+open D, "$etc/tape.$tapeid" or die "Unknown tape $tapeid ($!).\n";
+for (;;) {
+    $_= <D> or die; chomp; s/\s+$//;
+    last if m/^end$/;
+    next unless m/\S/;
+    next if m/^\#/;
+    if (m/^filesystems (\w+)$/) {
+       $fsys= $1;
+    } elsif (m/^next (\w+)$/) {
+       $next= $1;
+    } elsif (m/^incremental$/) {
+       $incremental= 1;
+    } else {
+       die "unknown entry in tape $tapeid at line $.: $_\n";
+    }
+}
+close D or die $!;
+
+# Incremental backups are handled by increm, not us.
+if ($incremental) {
+    die "incremental tape $tapeid has next or filesystems\n"
+       if defined($next) || defined($fsys);
+    print STDERR "Incremental tape $tapeid.\n\n";
+    setstatus "FAILED during incremental startup";
+    exec "increm",$tapeid,$tapedesc;
+    die $!;
+}
+
+# Read the filesystem group definition (file fsys.nnn)
+readfsys("$fsys");
+
+$doing= "dump of $fsys to tape $tapedesc in drive $tape";
+print LOG "$doing:\n" or die $!;
+
+if (!$noreten) {
+    setstatus "FAILED retensioning";
+    runsystem("mt -f $tape reten");
+}
+
+setstatus "FAILED writing tape ID";
+# First write the tape ID to this tape.
+
+writetapeid($tapeid,$tapedesc);
+
+unlink 'this-md5sums';
+
+print "Doing $doing ...\n" or die $!;
+
+unlink 'p';
+system 'mknod -m600 p p'; $? and die $?;
+
+setstatus "FAILED during dump";
+
+sub closepipes () {
+    close(DUMPOR); close(TEEOR); close(BUFOR); close(FINDOR);
+    close(DUMPOW); close(TEEOW); close(BUFOW); close(FINDOW);
+    close(GZOR); close(GZOW);
+    close(DDERRR); close(DDERRW);
+}
+
+# work out a find option string that will exclude the required files    
+# Note that dump pays no attention to exclude options.
+$exclopt = '';
+foreach $exc (@excldir) {
+    $exclopt .= "-regex $exc -prune -o ";
+}
+foreach $exc (@excl) {
+    $exclopt .= "-regex $exc -o ";
+}
+
+# For each filesystem to be put on this tape:
+for $tf (@fsys) {
+    printdate();
+    parsefsys();
+    prepfsys();
+
+    pipe(FINDOR,FINDOW) or die $!;
+    pipe(DUMPOR,DUMPOW) or die $!;
+    pipe(TEEOR,TEEOW) or die $!;
+    pipe(TEEOR,TEEOW) or die $!;
+    pipe(BUFOR,BUFOW) or die $!;
+    pipe(DDERRR,DDERRW) or die $!;
+    
+    $bufir='TEEOR';
+    $ddcmd= "dd ibs=$softblocksizebytes obs=$blocksizebytes of=$ntape 2>&1";
+
+    if ($gz) {
+       $bufir='GZOR';
+       pipe(GZOR,GZOW) or die $!;
+       $ddcmd .= " conv=sync";
+    }
+    
+    nexttapefile("full $prefix:$atf_print");
+
+    # We can back up via dump or cpio or zafio
+    $dumpin= '</dev/null';
+    if ($tm eq 'dump') {
+       $dumplabel= $pcstr.$atf_print.'$';
+       $dumpcmd= "dump 0Lbfu $dumplabel $softblocksizekb - $atf";
+    } elsif ($tm eq 'cpio') {
+       startprocess '</dev/null','>&FINDOW',$rstr."find $atf -xdev -noleaf -print0";
+       $dumpcmd= "cpio -Hustar -o0C$softblocksizebytes";
+       $dumpin= '<&FINDOR';
+    } elsif ($tm eq 'zafio') {
+        # compress-each-file-then-archive using afio
+        startprocess '</dev/null','>&FINDOW',$rstr."find $atf -xdev -noleaf $exclopt -print";
+        # don't use verbose flag as this generates 2MB report emails :->
+        $dumpcmd = "afio -b $softblocksizebytes -Zo -";
+        $dumpin = '<&FINDOR';
+    } elsif ($tm eq 'ntfsimage') {
+       $dumpcmd= "ntfsimage -svvf --dirty $dev";
+    } elsif ($tm eq 'gtar') {
+       execute("$rstr touch $fsidfile+new");
+       $dumpcmd= "tar Ccfl $atf - .";
+    } else {
+       die "unknown method $tm for $prefix:$atf_print\n";
+    }
+    # This is a funky way of doing a pipeline which pays attention
+    # to the exit status of all the commands in the pipeline.
+    # It is roughly equivalent to:
+    #    md5sum <p >>this-md5sums
+    #    dump <$dumpin | tee p [| gzip] | writebuffer | dd >/dev/null
+
+    startprocess '<p','>>this-md5sums',"$nice md5sum";
+    startprocess $dumpin,'>&DUMPOW',"$nice ".$rstr.$dumpcmd;
+    startprocess '<&DUMPOR','>&TEEOW',"$nice tee p";
+    if ($gz) {
+       startprocess '<&TEEOR','>&GZOW',"$nice gzip -v$gz";
+    }
+    startprocess "<&$bufir",'>&BUFOW',"$nasty writebuffer";
+    startprocess '<&DDERRR','>/dev/null',"$nice tee dderr >&2";
+    startprocess '<&BUFOR','>&DDERRW',"$nasty $ddcmd";
+    closepipes();
+    endprocesses();
+
+    open DDERR, "dderr" or die $!;
+    defined(read DDERR,$_,1023) or die $!;
+    close DDERR;
+    m/\n(\d+)\+0 records out\n/ or die ">$dderr< ?";
+    push @tapefilesizes, [ $1, $currenttapefilename ];
+    $totalrecords += $1;
+    pboth("total blocks written so far: $totalrecords\n");
+
+    if ($tm eq 'gtar') {
+       execute("$rstr mv -f $fsidfile+new $fsidfile");
+    }  
+    
+    finfsys();
+}
+
+# The backup should now be complete; verify it
+
+setstatus "FAILED during check";
+
+# Rewind the tape and skip the TAPEID record
+runsystem("mt -f $tape rewind");
+runsystem("mt -f $ntape fsf 1");
+
+# Check the md5sums match for each filesystem on the tape
+open S,"this-md5sums" or die $!;
+for $tf (@fsys) {
+    printdate();
+    parsefsys();
+    chomp($orgsum= <S>); $orgsum =~ s/\ +\-?$//;
+    $orgsum =~ m/^[0-9a-fA-F]{32}$/i or die "orgsum \`$orgsum' ?";
+    $cmd= "$nasty dd if=$ntape ibs=$blocksizebytes";
+    $cmd .= " | $nasty readbuffer";
+    $cmd .= " | $nice gzip -vd" if $gz;
+    $cmd .= " | $nice md5sum";
+    pboth("  $cmd\n");
+    chomp($csum= `$cmd`);
+    $csum =~ s/\ +\-?$//;
+    $orgsum eq $csum or die "MISMATCH $tf $csum $orgsum\n";
+    print "checksum ok $csum\t$tf\n" or die $!;
+    print LOG "checksum ok $csum\t$tf\n" or die $!;
+}
+printdate();
+runsystem("mt -f $tape rewind");
+
+setstatus "FAILED during cleanup";
+
+$summary= '';
+foreach $tfs (@tapefilesizes) {
+    $summary .= sprintf "    %10d blocks for %s\n", $tfs->[0], $tfs->[1]
+}
+$summary .=
+    sprintf "    %10d blocks total (of %d bytes) plus TAPEID and headers\n",
+    $totalrecords, $blocksizebytes;
+
+pboth("size-summary:\n");
+pboth($summary);
+
+open SS, ">size-summary..new" or die $!;
+print SS $summary or die $!;
+close SS or die $!;
+rename 'size-summary..new',"size-summary.$fsys" or die $!;
+
+# Write to some status files to indicate what the backup system
+# ought to do when next invoked.
+# reset incremental backup count to 1.
+open IAN,">increm-advance.new" or die $!;
+print IAN "1\n" or die $!;
+close IAN or die $!;
+
+# Next full backup is whatever the next link in the tape description
+# file says it ought to be.
+open TN,">next-full.new" or die $!;
+print TN "$next\n" or die $!;
+close TN or die $!;
+
+unlink 'last-tape','next-full';
+# We are the last tape to have been backed up
+rename 'TAPEID','last-tape' or die $!;
+rename 'this-md5sums',"md5sums.$fsys" or die $!;
+rename 'log',"log.$fsys" or die $!;
+rename 'next-full.new',"next-full" or die $!;
+rename 'increm-advance.new',"increm-advance" or die $!;
+
+print "$doing completed.\nNext dump tape is $next.\n" or die $!;
+
+setstatus "Successful: $tapedesc $fsys, next $next";
+exit 0;
diff --git a/backup/increm b/backup/increm
new file mode 100755 (executable)
index 0000000..c9d52ca
--- /dev/null
@@ -0,0 +1,151 @@
+#!/usr/bin/perl
+# increm
+# Do an incremental backup; ONLY for invocation by full !
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+# We are invoked by full if the tape description file says that it is
+# for an incremental backup.  We expect two commandline argument which
+# is the ID string of the tape to use, and the description (which
+# includes ID and function).
+
+BEGIN {
+    $etc= '/etc/chiark-backup';
+    require "$etc/settings.pl";
+    require 'backuplib.pl';
+}
+
+$|=1;
+
+@ARGV==2 or die;
+($tapeid,$tapedesc)= @ARGV;
+
+print "Running incremental onto $tapedesc ...\n" or die $!;
+
+# Just check that we weren't passed a bogus ID (the tape description
+# file for incrementals is just 'incremental\nend\n')
+open T,"$etc/tape.$tapeid" or die "Tape $tapeid not found: $!\n";
+close T;
+
+# This is only used for the 'next FULL backup is X' message at the end.
+open NF,"next-full" or die $!;
+chomp($next= <NF>);
+close NF or die $!;
+
+setstatus "FAILED during incremental";
+
+# Read the number of the incremental involved
+# (ie, (how many files are already on the tape) - 1)
+open A,"increm-advance" or die $!;
+chomp($advance= <A>);
+close A or die $!;
+
+# better be a decimal number
+$advance =~ m/^\d+$/ or die "$advance ?";
+
+# Get a list of all filesystems
+readfsys('all');
+openlog();
+
+# Rewind the tape and if we are the first incremental on the tape then
+# write the TAPEID record, otherwise skip forward to the correct point.
+# (full will already have checked that this is the right tape before
+# it invoked us, so no need to read the existing TAPEID record first.)
+runsystem("mt -f $ntape rewind");
+if ($advance == 1) {
+    writetapeid($tapeid,$tapedesc);
+} else {
+    runsystem("mt -f $ntape fsf $advance");
+    $currenttapefilenumber= $advance;
+}
+
+sub closepipes () {
+    close(DUMPOR); close(BUFOR);
+    close(DUMPOW); close(BUFOW);
+    close(GZOR); close(GZOW);
+}
+
+setstatus "PROBLEMS during incremental dump";
+
+for $tf (@fsys) {
+
+    parsefsys();
+    prepfsys();
+    
+    $bufir='DUMPOR';
+    $ddcmd= "$nasty dd ibs=$softblocksizebytes obs=$blocksizebytes of=$ntape";
+
+    pipe(DUMPOR,DUMPOW) or die $!;
+    pipe(BUFOR,BUFOW) or die $!;
+
+    $gz= $gzi if length $gzi;
+    if ($gz) {
+       $bufir='GZOR';
+       pipe(GZOR,GZOW) or die $!;
+       $ddcmd .= " conv=sync";
+    }
+
+    if ($dopt{'noinc'}) {
+       pboth("Incrementals of $atf_print ($prefix) suppressed in config.\n");
+    }
+
+    if ($tm eq 'dump') {
+       $dumplabel= $pcstr.$atf_print.'$';
+       $dumpcmd= "dump 1Lbfu $dumplabel $softblocksizekb - $atf";
+    } elsif ($tm eq 'gtar') {
+       $dumpcmd= "tar NCcfl $fsidfile $atf - .";
+    } else {
+       pboth("Not dumping $atf_print ($prefix) - not supported.\n");
+       next;
+    }
+
+    nexttapefile("inc $prefix:$atf_print");
+    
+    # Same trick as full uses to do a pipeline whilst keeping track
+    # of all exit statuses:
+    #   dump </dev/null | writebuffer | dd >/dev/null
+    startprocess '</dev/null','>&DUMPOW',"$nice ".$rstr.$dumpcmd;
+    if ($gz) {
+       startprocess '<&DUMPOR','>&GZOW',"$nice gzip -v$gz";
+    }
+    startprocess "<&$bufir",'>&BUFOW',"$nasty writebuffer";
+    startprocess '<&BUFOR','>/dev/null',"$nasty $ddcmd";
+    closepipes();
+    endprocesses();
+    # advance is a file counter, so it needs to be updated for each 
+    # dump we do to tape.
+    $advance++;
+
+    finfsys();
+}
+
+# Rewind the tape, and increment the counter of incremental backups.
+runsystem("mt -f $tape rewind");
+open IAN,">increm-advance.new" or die $!;
+print IAN "$advance\n" or die $!;
+close IAN or die $!;
+rename 'increm-advance.new','increm-advance' or die $!;
+
+pboth("Next FULL dump tape is $next\n");
+
+setstatus "INCREMENTAL successful: $tapedesc, next full is $next";
+exit 0;
diff --git a/backup/iwjbackup.txt b/backup/iwjbackup.txt
new file mode 100644 (file)
index 0000000..a353e75
--- /dev/null
@@ -0,0 +1,204 @@
+iwjbackup.txt
+documentation file
+Copyright - AND NO WARRANTY - see notes at bottom of file for details.
+
+This is a quick summary of the backup scripts, and some comments on
+some of the config files: it's a bit patchy and might have the odd
+ommission. The canonical source is the sources, as always :->
+
+
+To run, the contents of /etc/chiark-backup should be:
+
+warnings.*: files defining how many warnings you get as the system is
+brought down to do backups. The defaults are fine.
+
+settings.pl: generic config file: in particular, the name of the tape
+device is set here.
+
+settings.sh: generic config file for shell scripts.  Currently only
+contains some options for the lvm snapshotter.
+
+tape.*: conventionally, each tape you're going to use in the backup
+cycle has a tape number, a name and a config file.  The tape numbers
+in use at Relativity are digit strings like `512'.  The name is a
+combination of rotation set and volume number; rotation sets are
+typically a single letter (`s', `t', `u', `v') at Relativity and
+volumes a single digit (`0', `1', `2') at Relativity.  You need at
+least two tapes as the system won't write a backup on the same tape it
+wrote the last one to.
+
+There are also conventionally incremental tapes whose names are a
+fixed letter (`k' in the current scheme) followed by a rotation
+letter.  At Relativity we have two of these, `ks' and `kt'.
+
+Syntax of the tape.* files for full dump tapes:
+  filesystems X
+  next N
+  end
+
+where N is the name of the next tape in the *full dump* sequence
+(which should be circular; eg
+v0->v1->v2->s0->s1->s1->t0->t1->t2->u0->u1->u2->v0->...
+and X is a filesystem group name (typically the same as the volume
+number).
+
+Each defined filesystem group has a name and a config file
+fsys.<name>.  These files define what is backed up and how.  The
+filesystem `all' must also exist; it's used for incremental backups
+(and it must exist even if you don't do incrementals).
+
+In the fsys.* files:
+  Empty lines and lines starting '#' are comments and ignored.
+  Lines starting `excludedir' given regexps of things to exclude
+   (temp dirs, Netscape's cache, etc).
+  Lines starting `include' say to include another file when reading
+   this one.
+  Lines starting `prefix' give a command prefix necessary to
+   run things on a remote machine:
+     prefix <prefix-name> <command-part>
+  Other lines should be of the form
+    [<device name>:]<directory name> <backup-type>[,<options>]
+  for local backups, or
+    [<device name>:]<directory name> <backup-type>[,<options>] <prefix-name>
+  for remote backups.
+The file (including any included files) must end with the word 'end'
+on a line of its own.
+
+Valid values for <backup-type> are
+  cpio
+    uses cpio to produce tar-format backups
+  dump
+    uses dump to dump entire filesystems
+    <directory name> should be a mount-point
+  gtar
+    uses GNU tar to produce GNU tar format backups and -N-based
+    semi-incrementals (not --incremental or --listed-incremental)
+  zafio
+    uses afio to compress each file as it is backed up
+  ntfsimage
+    for NTFS volumes, requires device name
+Only `dump' and `gtar' type backups perform any kind of incremental
+backups.
+
+<options> is a comma-separated list of <option> or <option>=<value>.
+Options supported:
+
+  gz[i][=<compressionlevel>]
+    Indicates that the whole stream should be compressed with gzip.
+    The compression level defaults to 1 if gz is specified by the
+    level isn't.  gzi appliies only to the incrementals; gz applies to
+    both unless gzi is also specified.  compression level 0 means not
+    to run gzip at all and is the default if gz[i] is not mentioned.
+
+  snap=<snapkind>
+    Indicates that the filesystem should be frozen before the backup
+    by using /etc/chiark-backup/snap/<snapkind>.  See the head comment
+    in /etc/chiark-backup/snap/lvm for details of how this works.
+    When snap= is used, the block device must be specified.
+
+  noinc
+    Suppress incrementals.
+
+expected-diffs is a config file to indicate which 
+filesystems should *not* be backed up. The scripts do a config
+check which involves checking that:
+ * all filesystems to be backed up are present
+ * all filesystems that are present are backed up
+expected-diffs allows you to make exceptions to this; backing 
+up your CDROM drive is a bit pointless, frex.
+The format here is:
+<prefixchar><mountpoint>
+
+where <prefixchar> is ?, ! or nothing, and 
+<mountpoint> is <prefix>:<mountpoint> for a remote fs or
+<mountpoint> for a local one
+(examples: "mnementh:/cdrom", "/cdrom").
+If <prefixchar> is nothing, the scripts will complain if the fs
+is mounted. If it is !, they will complain if it is not mounted.
+If ? they won't complain either way (useful for devices that are
+not always mounted, like /cdrom).
+
+
+You may also create `bringup-hook', a script (or program) which will
+be run by `bringup' at the end.
+
+
+Useful scripts (all in /usr/bin):
+
+backup-checkallused: this only does a check of the configuration
+files.  It should give a cryptic summary of the configuration and
+print 'configuration ok'. If not, fix your config files :->
+You have to create the file /var/lib/chiark-backup/last-tape
+containing the id of a tape; this helps backup-checkallused know where
+to start iterating over tapes.  Any tapeid will do.  (But don't make
+it the same as the one you want to back up to first.)
+
+backup-loaded: this tells the scripts that a currently unlabelled tape
+should be treated as tape X: eg:
+    backup-loaded b3
+will cause it to treat it as tape `b3'.  NB: this won't override the
+TAPEID label written on the tape; it's just for use with previously
+unused tapes.  This applies only to the next time the backup scripts
+are invoked.  You can say just
+    backup-loaded
+to go back to the default behaviour, which is to fail if the tape has
+no TAPEID.
+
+backup-driver: this is the script to actually run to do a backup.  If
+run from the command line, give it the argument 'test' - otherwise it
+will attempt to run bringup to change runlevel, on the assumption that
+it was run from inittab (see below).  The status report email will be
+sent to whatever the unqualified local-part `dump-reports' points to.
+
+backup-takedown: This is for running a reduced level of system
+services during backups.  Usage: takedown <freq> where <freq> can be
+`now', `soon' or nothing depending on number of warning messages
+desired - these correspond to warnings.* files.
+
+To use this you'll need to configure init:
+ * set up runlevel 5 to provide the level of services you want
+   (by tweaking the symlinks in /etc/rc5.d or equivalent)
+ * Add the following to /etc/inittab (tweak paths and VC number
+   if desired):
+
+  # Runlevel 5 is set up to run a reduced level of services during
+  # backups. (currently this means: no squid, no webserver, no newsserver)
+  # We also run the backup script automatically on entering runlevel 5:
+  dm:5:once:backup-driver </dev/tty8 >/dev/tty8 2>&1
+
+ * takedown can be run from the command line or via cron.
+
+backup-whatsthis: a simple script to display the TAPEID of the current
+tape and optionally list its contents.  This script is a bit of a hack
+and may not be fully reliable:
+
+ Usage:
+ whatsthis [--list [n]]
+
+WARNING: it's currently hardwired to assume `cpio' type backups
+when listing; it could be trivially hardwired to assume `zafio' 
+or with slightly more effort it could be done properly :->.
+
+
+COPYRIGHT and LACK OF WARRANTY information
+
+This file is part of chiark backup, a system for backing up GNU/Linux and
+other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+
+chiark backup is:
+ Copyright (C) 1997-1998,2000-2001,2007
+                    Ian Jackson <ian@chiark.greenend.org.uk>
+ Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+
+This 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 3, or (at your option) any later version.
+
+This 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, consult the Free Software Foundation's
+website at www.fsf.org, or the GNU Project website at www.gnu.org.
diff --git a/backup/labeltape b/backup/labeltape
new file mode 100755 (executable)
index 0000000..fc3d2b3
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use POSIX;
+
+$etc= '/etc/chiark-backup';
+require "$etc/settings.pl";
+require 'backuplib.pl';
+
+while ($ARGV[0] =~ m/^-/) {
+    $_= shift @ARGV;
+    last if m/^\-$/;
+    s/^\-//;
+    while (length) {
+       if (s/^f//) {
+           $force=1;
+       } else {
+           die "$0: unknown option -$_\n";
+       }
+    }
+}
+
+@ARGV==1 or die "$0: need 1 arg, new TAPEID\n";
+($newid)= @ARGV;
+
+open LOG, ">/dev/null" or die $!;
+
+readtapeid_raw();
+
+if (!open T,'TAPEID') {
+    $!==&ENOENT or die $!;
+} else {
+    chomp($oldid= <T>);
+    close T or die $!;
+    print "Tape is currently labelled \`$oldid'\n" or die $!;
+    die "$0: use -f to force relabelling\n" unless $force;
+}
+
+open T,'>TAPEID' or die $!;
+print T "$newid\n" or die $!;
+close T or die $!;
+
+writetapeid($newid,'tapeid set manually');
+rewind_raw();
+
+print "Labelled tape \`$newid'\n" or die $!;
+exit 0;
diff --git a/backup/loaded b/backup/loaded
new file mode 100755 (executable)
index 0000000..7670e06
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh
+# loaded
+# Entry point for sysadmin to state that we've loaded a tape
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+set -e
+cd /var/lib/chiark-backup
+
+if test -f "/etc/chiark-backup/tape.$1"
+then
+       echo "$1" >really-TAPEID
+       echo "Will assume tape is $1 unless I discover otherwise."
+else
+       if [ "x$1" != x ]; then echo "Tape $1 not found."; fi
+       echo "Will only use tape if it has a TAPEID."
+       rm -f really-TAPEID
+fi
diff --git a/backup/lvm b/backup/lvm
new file mode 100755 (executable)
index 0000000..dd503e7
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/bash
+# invoked by backup scripts as
+#    lvm snap $vardir $device $mountpoint
+#              creates and mounts on $vardir/snap-mount
+#              creates $vardir/snap-device -> device
+#    lvm drop $vardir
+
+set -e
+snapkind=lvm
+. /usr/share/chiark-backup/snap-common
+
+#---------- clean up anything
+
+lvmdropcore
+
+if test "$opmode" = drop; then
+       echo 'lvm snap dropped'
+       exit 0
+fi
+
+#---------- create snapshot
+
+fstype="$(mount | sed -n \
+ "s,^$device on $mountpoint type \([a-z0-9][a-z0-9]*\) .*,-t \1 ,p")"
+
+lvmunmapperdevice
+lvmdevice2vgroup
+
+if [ -z "$lvm_lvsize_opts" ]; then
+       lvmextentscore1
+
+       lvdisplay_out="$(really lvdisplay -c "$device")"
+       extents2="$(printf "%s" "$lvdisplay_out" | awk -F: '{print $8}')"
+       extents2=$(( $extents2 + ($extents2+9)/10 - 1 ))
+
+       lvmextentscore2
+fi
+
+lvmcreatecore1
+
+lvcreate -s \
+       $lvm_lvtools_opts \
+       $lvm_lvsize_opts \
+       -n $lvm_lv \
+       $lvm_lvcreate_opts "$device" $lvm_lvcreate_args
+
+mkdir -- "$snmnt"
+mount -v -r $fstype $lvm_mount_opts "$lvpath" "$snmnt"
+
+echo 'lvm snap activated'
diff --git a/backup/man/checkallused.1 b/backup/man/checkallused.1
new file mode 100644 (file)
index 0000000..6224eeb
--- /dev/null
@@ -0,0 +1,33 @@
+.TH BACKUP-CHECKALLUSED "1" "July 2003" "Debian" "Chiark-backup"
+.SH NAME
+backup-checkallused \- check chiark-backup configuration
+.SH SYNOPSIS
+.B backup-checkallused 
+.br
+.SH DESCRIPTION
+`backup-checkallused' does a check of the configuration
+files.  It should give a cryptic summary of the configuration and
+print 'configuration ok'. If not, fix your config files :->
+You have to create the file 
+.br
+/var/lib/chiark-backup/last-tape
+containing the id of a tape; this helps backup-checkallused know where
+to start iterating over tapes.  Any tapeid will do.  (But don't make
+it the same as the one you want to back up to first.)
+.SH OPTIONS
+None
+.SH FILES
+.TP
+.I /etc/chiark-backup/settings.pl
+Configuration file for the whole of chiark-backup
+.P
+.SH AUTHOR
+This Manual page was written by Matthew Vernon <matthew@debian.org> but 
+may be used by anyone.
+.SH COPYRIGHT
+Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+.br
+Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+.br
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/backup/man/driver.1 b/backup/man/driver.1
new file mode 100644 (file)
index 0000000..96cf062
--- /dev/null
@@ -0,0 +1,31 @@
+.TH BACKUP-DRIVER "1" "July 2003" "Debian" "Chiark-backup"
+.SH NAME
+backup-driver \- entry point for cron or inittab to start backups
+.SH SYNOPSIS
+.B backup-driver
+.I [-test]
+.br
+.SH DESCRIPTION
+`backup-driver' is the script to actually run to do a backup. It
+assumes that it is being run from cron or inittab unless passed the
+`test' option
+.SH OPTIONS
+.TP
+.BR -test
+Do not attempt to use backup-bringup to change runlevel. Use this
+argument when running backup-driver from the command line.
+.SH FILES
+.TP
+.I /etc/chiark-backup/settings.pl
+Configuration file for the whole of chiark-backup
+.P
+.SH AUTHOR
+This Manual page was written by Matthew Vernon <matthew@debian.org> but 
+may be used by anyone.
+.SH COPYRIGHT
+Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+.br
+Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+.br
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/backup/man/labeltape.1 b/backup/man/labeltape.1
new file mode 100644 (file)
index 0000000..0585899
--- /dev/null
@@ -0,0 +1,34 @@
+.TH BACKUP-LABELTAPE "1" "July 2003" "Debian" "Chiark-backup"
+.SH NAME
+backup-labeltape \- display or change tape label
+.SH SYNOPSIS
+.B backup-labeltape
+.I [-force] TAPEID
+.br
+.SH DESCRIPTION
+`backup-labeltape' will display the tape label; it will also label the
+tape with TAPEID unless the tape already has a label (the -force
+option will over-write the current label)
+`test' option
+.SH OPTIONS
+.TP
+.BR -force
+Over-write an existing tape label
+.TP
+.BR TAPEID
+The new label to write to the tape
+.SH FILES
+.TP
+.I /etc/chiark-backup/settings.pl
+Configuration file for the whole of chiark-backup
+.P
+.SH AUTHOR
+This Manual page was written by Matthew Vernon <matthew@debian.org> but 
+may be used by anyone.
+.SH COPYRIGHT
+Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+.br
+Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+.br
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/backup/man/loaded.1 b/backup/man/loaded.1
new file mode 100644 (file)
index 0000000..c88603c
--- /dev/null
@@ -0,0 +1,41 @@
+.TH BACKUP-LOADED "1" "July 2003" "Debian" "Chiark-backup"
+.SH NAME
+backup-loaded \- tell the chiark-backup system what to do with a new tape
+.SH SYNOPSIS
+.B backup-loaded
+.I [TAPEID]
+.br
+.SH DESCRIPTION
+backup-loaded: this tells the scripts that a currently unlabelled tape
+should be treated as tape X: eg:
+.br
+\fBbackup-loaded b3\fP
+.br
+will cause it to treat it as tape `b3'.  NB: this won't override the
+TAPEID label written on the tape; it's just for use with previously
+unused tapes.  This applies only to the next time the backup scripts
+are invoked.  You can say just
+.br
+\fBbackup-loaded\fP
+.br
+to go back to the default behaviour, which is to fail if the tape has
+no TAPEID.
+.SH OPTIONS
+.TP
+.BR TAPEID
+Treat the tape as label TAPEID
+.SH FILES
+.TP
+.I /etc/chiark-backup/settings.pl
+Configuration file for the whole of chiark-backup
+.P
+.SH AUTHOR
+This Manual page was written by Matthew Vernon <matthew@debian.org> but 
+may be used by anyone.
+.SH COPYRIGHT
+Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+.br
+Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+.br
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/backup/man/takedown.1 b/backup/man/takedown.1
new file mode 100644 (file)
index 0000000..e435932
--- /dev/null
@@ -0,0 +1,35 @@
+.TH BACKUP-TAKEDOWN "1" "July 2003" "Debian" "Chiark-backup"
+.SH NAME
+backup-takedown \- Bring some system services down for backups
+.SH SYNOPSIS
+.B backup-takedown
+.I [freq]
+.br
+.SH DESCRIPTION
+`backup-takedown' is for running a reduced level of system
+services during backups. To use this command you need to configure
+init by setting up runlevel 5 to provide the level of services you
+want, and to run backup-driver automatically on entering runlevel 5.
+.SH OPTIONS
+.TP
+.BR freq
+`freq' may be `now', `soon' or nothing depending on the number of
+warning messages desired - these correspond to warnings.* files.
+.SH FILES
+.TP
+.I /etc/chiark-backup/warnings.*
+Files specifying what number and frequency of warnings will be produced.
+.TP
+.I /etc/chiark-backup/settings.pl
+Configuration file for the whole of chiark-backup
+.P
+.SH AUTHOR
+This Manual page was written by Matthew Vernon <matthew@debian.org> but 
+may be used by anyone.
+.SH COPYRIGHT
+Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+.br
+Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+.br
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/backup/man/whatsthis.1 b/backup/man/whatsthis.1
new file mode 100644 (file)
index 0000000..32e7366
--- /dev/null
@@ -0,0 +1,37 @@
+.TH BACKUP-WHATSTHIS "1" "July 2003" "Debian" "Chiark-backup"
+.SH NAME
+backup-whatsthis \- read an id off a tape and display it
+.SH SYNOPSIS
+.B backup-whatsthis
+.RB [\| --list
+.RI [\| n \|]]
+.br
+.SH DESCRIPTION
+`backup-whatsthis' is a simple script to display the TAPEID of the current
+tape and optionally list its contents.  This script is a bit of a hack
+and may not be fully reliable.
+.SH OPTIONS
+.TP
+.B \--list
+.RI [\| n \|]
+Print TAPEID then list archive n (default 0). Note that archives are
+numbered from zero.
+.SH FILES
+.TP
+.I /etc/chiark-backup/settings.pl
+Configuration file for the whole of chiark-backup
+.P
+.SH BUGS
+`backup-whatsthis' is currently hardwired to assume `cpio' type backups
+when listing; it could be trivially hardwired to assume `zafio' 
+or with slightly more effort it could be done properly :->.
+.SH AUTHOR
+This Manual page was written by Matthew Vernon <matthew@debian.org> but 
+may be used by anyone.
+.SH COPYRIGHT
+Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+.br
+Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+.br
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/backup/nosnap b/backup/nosnap
new file mode 100755 (executable)
index 0000000..06187fa
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+set -e
+
+removes () {
+       if test -L "$vardir/snap-mount"; then
+               rm -f -- "$vardir/snap-mount"
+       elif test -d "$vardir/snap-mount"; then
+               rmdir -- "$vardir/snap-mount"
+       fi
+       rm -f -- "$vardir/snap-device"
+}
+
+vardir="$2"
+
+case "$#.$1" in
+2.drop)
+       removes
+       ;;
+4.snap)
+       removes
+       ln -s -- "$3" "$vardir/snap-device"
+       ln -s -- "$4" "$vardir/snap-mount"
+       ;;
+*)
+       cat >&2 <<'END'
+usage: .../nosnap snap VARDIR DEV MOUNT
+       .../nosnap drop VARDIR
+END
+       exit 1
+       ;;
+esac
diff --git a/backup/remount b/backup/remount
new file mode 100755 (executable)
index 0000000..ce7a4bb
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+set -e
+
+removes () {
+       rm -f -- "$vardir/snap-mount" "$vardir/snap-device"
+}
+
+vardir="$2"
+
+case "$#.$1" in
+2.drop)
+       fs="$(readlink "$vardir/snap-mount")"
+       removes
+       mount -vo remount,rw "$fs" || true
+       ;;
+4.snap)
+       removes
+       mount -vo remount,ro "$4"
+       ln -s -- "$3" "$vardir/snap-device"
+       ln -s -- "$4" "$vardir/snap-mount"
+       ;;
+*)
+       cat >&2 <<'END'
+usage: .../remount snap VARDIR DEV MOUNT
+       .../remount drop VARDIR
+END
+       exit 1
+       ;;
+esac
diff --git a/backup/remountrocp b/backup/remountrocp
new file mode 100755 (executable)
index 0000000..04ddd27
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/bash
+# invoked by backup scripts as
+#    remountrocp snap $vardir $device $mountpoint
+#              remounts $mountpoint readonly
+#              copies data to $vardir/snap-mount
+#              remounts $mountpoint readwrite
+#    remountrocp drop $vardir
+#              deletes $vardir/snap-mount
+
+set -e
+snapkind=remountrocp
+: ${lvm_vg:=}
+remountrocp_fs=ext2
+. ${CHIARK_BACKUP_SHAREDIR:-/usr/share/chiark-backup}/snap-common
+
+#---------- clean up anything
+
+vgroup=$lvm_vg
+lvmdropcore
+
+lastsettings="$vardir/remountrocp-settings"
+test ! -f $lastsettings || . $lastsettings
+
+if test "$opmode" = drop; then
+       test -z "$last_mountpoint" || mount -o remount,rw $last_mountpoint
+       rm -f $lastsettings
+       echo 'remountrocp snap dropped'
+       exit 0
+fi
+
+#---------- create snapshot
+
+if [ -z "$lvm_lvsize_opts" ]; then
+       lvmextentscore1
+
+       df_out="$(really df -P --block-size=$extsize $mountpoint)"
+       extents2="$(printf "%s" "$df_out" | awk '/^\// {print $3}')"
+       extents2=$(( ($extents2*150+102399)/102400 + 4 ))
+
+       lvmextentscore2
+fi
+
+lvmcreatecore1
+
+cat >$lastsettings.new <<END
+last_mountpoint=$mountpoint
+END
+mv -f $lastsettings.new $lastsettings
+
+lvcreate \
+       $lvm_lvtools_opts \
+       $lvm_lvsize_opts \
+       -n $lvm_lv \
+       $lvm_lvcreate_opts \
+       $vgroup \
+       $lvm_lvcreate_args
+
+mkfs -t $remountrocp_fs -q "$lvpath"
+
+mkdir -- "$snmnt"
+mount -t $remountrocp_fs $lvm_mount_opts "$lvpath" "$snmnt"
+echo ' copy filesystem created and mounted'
+
+attempts=10
+while true; do
+       if mount -o remount,ro "$mountpoint"; then break; fi
+       attempts=$(( $attempts - 1 ))
+       if [ $attempts = 0 ]; then
+               echo >&2 'cannot remount readonly'
+               exit 1
+       fi
+       sleep 1
+done
+trap "set +e; mount -o remount,rw $mountpoint; exit 12" 0
+echo ' source remounted readonly, copying...'
+cp -ax -- "$mountpoint/." "$snmnt/."
+echo ' finalising...'
+mount -o remount,rw "$mountpoint"
+trap '' 0
+mount -o remount,ro "$lvpath"
+
+echo 'remountrocp snap activated'
diff --git a/backup/snap-common b/backup/snap-common
new file mode 100644 (file)
index 0000000..52a2c41
--- /dev/null
@@ -0,0 +1,89 @@
+# sourced by snap/lvm and snap/remountrocp
+
+#---------- common arg parsing
+
+nargs=$#
+opmode="$1"
+vardir="$2"
+device="$3"
+mountpoint="$4"
+
+lvm_lv=chiark-backup
+lvm_lvtools_opts='-A n'
+lvm_lvcreate_opts=
+lvm_lvcreate_args=
+
+test ! -f /etc/chiark-backup/settings.sh || . /etc/chiark-backup/settings.sh
+
+case "$nargs.$opmode" in
+4.snap|2.drop)
+       ;;
+*)
+       cat >&2 <<END
+usage: .../$snapkind snap VARDIR DEV MOUNT
+       .../$snapkind drop VARDIR
+END
+       exit 1
+       ;;
+esac
+
+#---------- common functions
+
+lvmunmapperdevice () {
+       # turns device=/dev/mapper/... into /dev/<group>/<volume>
+       case "$device" in
+       /dev/mapper/*)
+               device="`printf '%s' "$device" | perl -pe '
+                       s,^/dev/mapper/,,;
+                       die if m,/,;
+                       s,\-\-,!,g;
+                       s,\-,/,g;
+                       s,\!,-,g;
+                       s,^,/dev/,;
+               '`"
+               ;;
+       esac
+}
+
+lvmdevice2vgroup () {
+       vgroup="${device#/dev/}"
+       vgroup="${vgroup%/*}"
+}
+
+lvmdropcore () {
+       snmnt="$vardir/snap-mount"
+       umount -v "$snmnt" || true
+       test ! -d "$snmnt" || rmdir -- "$snmnt" || rm -f "$snmnt"
+
+       set +e
+       old_lv_dev="$(readlink $vardir/snap-device)"
+       rc=$?
+       set -e
+
+       if [ $rc = 0 ]; then
+               set +e
+               lvchange    $lvm_lvtools_opts -a n $old_lv_dev
+               lvremove -f $lvm_lvtools_opts      $old_lv_dev
+               set -e
+               rm $vardir/snap-device
+       fi
+}
+
+lvmextentscore1 () {
+       # vgroup must be set
+       vgdisplay_out="$(really vgdisplay -c "$vgroup")"
+       extents="$(printf "%s" "$vgdisplay_out" | awk -F: '{print $16}')"
+       extsize="$(printf "%s" "$vgdisplay_out" | awk -F: '{print $13}')"
+}
+
+lvmextentscore2 () {
+       if [ $extents2 -lt $extents ]; then extents=$extents2; fi
+       lvm_lvsize_opts="-l $extents"
+}
+
+lvmcreatecore1 () {
+       # vgroup must be set
+       lvpath="/dev/$vgroup/$lvm_lv"
+       ln -s -- "$lvpath" "$vardir"/snap-device
+       sync
+}
diff --git a/backup/snap-drop b/backup/snap-drop
new file mode 100755 (executable)
index 0000000..85c443d
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+set -e
+vd=/var/lib/chiark-backup
+if [ "x$1" != "x" ]; then
+       vd="$1"; shift
+fi
+cd "$vd"
+test -f snap-drop || exit 0
+sh -x snap-drop || true
+rm snap-drop
diff --git a/backup/snaprsync b/backup/snaprsync
new file mode 100755 (executable)
index 0000000..82ea5d4
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/bash
+#
+# usage: snaprsync <setting>... <positionals>
+#  <setting> is --<name>=<value>
+#  <positionals> are assigned to unused mandatory values in order
+# mandatory:
+#   rhost device mountpoint localarea 
+# optional:
+       localprevious=
+       snapkind=lvm
+       rsharedir=/usr/share/chiark-backup 
+       retcdir=/etc/chiark-backup
+       rvardir=/var/lib/chiark-backup
+       bwlimit=
+       subdir=.
+       rsyncopts=
+       rsynccompress=z
+       sshopts=
+       summer=summer
+
+
+# snaprsync
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+
+set -e
+
+badusage () { echo >&2 "snaprsync: bad usage: $1"; exit 12; }
+nb_echo () { (echo "$@"); } # See Debian #382798
+x () { nb_echo "+ $@"; "$@"; }
+xspawned () { eval "${1}pid=$!; nb_echo \"+[$!] ($1) &\";"; }
+xwait () { eval "nb_echo \"+[\$${1}pid] ($1)...\"; wait \$${1}pid;"; }
+
+while true; do
+       case "$1" in
+       --?*=*)
+               name=${1#--}; name=${name%%=*}
+               value=${1#--*=}
+               case "$name" in
+               rhost|device|mountpoint|localarea);;
+               localprevious|snapkind|rsharedir|retcdir|rvardir|bwlimit);;
+               subdir|rsyncopts|rsynccompress|sshopts|summer);;
+               *) badusage "unknown setting $name";;
+               esac
+               eval "$name=\$value"
+               ;;
+       --)     shift; break ;;
+       -*)     badusage "unknown option $1" ;;
+       *)      break ;;
+       esac
+       shift
+done
+
+for name in rhost device mountpoint localarea; do
+       eval "value=\$$name"
+       if [ "x$value" != x ]; then continue; fi
+       if [ $# = 0 ]; then badusage "no value for setting $name"; fi
+       eval "$name=$1"
+       shift
+done
+
+datefmt='%Y-%m-%d %H:%M:%S Z'
+rsync="rsync ${bwlimit:+--bwlimit} $bwlimit"
+export RSYNC_RSH="ssh -o compression=no $sshopts"
+sshpfx='PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin; export PATH; '
+
+ssh $sshopts $rhost "$sshpfx date -u '+$rhost $datefmt start'"
+ssh $sshopts $rhost "$sshpfx id"
+ssh $sshopts $rhost "$sshpfx ls -d $rsharedir"
+ssh $sshopts $rhost "$sshpfx ls -d $rvardir"
+
+test -d $localarea || x mkdir $localarea
+ournode=`uname -n`
+rsumsfile=for-$ournode.sums
+summer="$summer -ACDBtqfx"
+
+td=/dev/enoent
+rc=12
+trap 'rm -rf $td; exit $rc' 0
+td=`mktemp -td`
+
+mkfifo -m 600 $td/sentinel
+exec 4<>$td/sentinel
+
+x ssh $sshopts $rhost "$sshpfx $rsharedir/snap-drop $rvardir"
+x ssh $sshopts $rhost "
+       $sshpfx
+       set -e
+       cd $rvardir
+       echo '$retcdir/snap/$snapkind drop $rvardir' >snap-drop.new
+       mv snap-drop.new snap-drop
+"
+x ssh $sshopts $rhost "$sshpfx $retcdir/snap/$snapkind snap $rvardir $device $mountpoint"
+ssh $sshopts $rhost <$td/sentinel 4<&- "
+  $sshpfx
+  set -e
+  date -u '+$rhost $datefmt main'
+  exec 3<&0 0</dev/null
+  (set +e; read x <&3; kill 0) &
+  cd $rvardir
+  umask 077
+  exec 3>$rsumsfile
+  cd snap-mount
+  $summer . >&3
+  date -u '+$rhost $datefmt sumsdone'
+  cd ..
+" &
+xspawned rsum
+x $rsync -aHSx$rsynccompress --numeric-ids --delete $rsyncopts \
+       ${localprevious:+--link-dest} $localprevious \
+       $rhost:$rvardir/snap-mount/$subdir $localarea/.
+date -u "+ $datefmt rsyncdone"
+
+exec 3>$localarea,lsums
+(cd $localarea && \
+ $summer . >&3) &
+xspawned lsum
+exec 3>&-
+
+xwait rsum
+exec 4<&-
+date -u "+ $datefmt sumsdone"
+x ssh $sshopts $rhost "$sshpfx $rsharedir/snap-drop $rvardir"
+
+if [ "x${localprevious}" != x ] && test -f "$localprevious,rsums"; then
+       cp "$localprevious,rsums" "$localarea,rsums"
+fi
+x $rsync -pI \
+       $rhost:$rvardir/$rsumsfile \
+       "$localarea,rsums"
+
+xwait $lsum
+date -u "+ $datefmt checking"
+
+set +e
+diff -u <(sed -e 's/^mountpoint/dir       /' "$localarea,rsums") \
+  "$localarea,lsums" >"$localarea,sumsdiff"
+diffrc=$?
+set -e
+test $diffrc = 0 || test $diffrc = 1
+
+date -u "+ $datefmt checked $diffrc"
+rc=$diffrc
diff --git a/backup/takedown b/backup/takedown
new file mode 100755 (executable)
index 0000000..97a2a9f
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+# takedown
+# Entry point for cron to take the system down for backups
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+# Expects a single (possibly empty) argument X which is used to select
+# a file /etc/chiark-backup/warnings.X. This file will contain lines like:
+# T 300 "in 10 minutes"
+# T 240 "in 5 minutes"
+# T 45 "in 1 minute"
+# T 15 "in 15 seconds"
+# configuring the frequency of warning messages. If you call the 
+# files 'warnings.soon', 'warnings.now' and 'warnings.' then
+# you can invoke this as:
+#   takedown                     lots of warnings
+#   takedown soon                not so many warnings
+#   takedown now                 no warning at all
+
+set -e
+cd /etc/chiark-backup
+
+host="`hostname`" || true
+
+T () {
+       (
+               exec wall <<END &
+ *** WARNING - SYSTEM GOING DOWN FOR BACKUPS ***
+ $host will shut down automatically $2.
+
+END
+       ) &
+       sleep $1
+}
+
+. "warnings.$1"
+
+(
+       exec wall <<END &
+ *** WARNING - SYSTEM GOING DOWN FOR BACKUPS ***
+
+  $host is shutting down IMMEDIATELY.
+
+END
+) &
+sleep 1
+
+# We assume that runlevel 5 is set up suitably for doing backups
+# (ie non-essential services turned off in an effort to get the
+# tape to stream.)
+telinit 5
diff --git a/backup/whatsthis b/backup/whatsthis
new file mode 100755 (executable)
index 0000000..26315f8
--- /dev/null
@@ -0,0 +1,143 @@
+#!/usr/bin/perl
+# whatsthis
+# read an id off the tape and display it to the user
+
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001,2007
+#                     Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+# First rough hack; mostly just code nabbed from full. 
+# --list assumes the dump type was 'zafio', which is a bit bogus.
+
+# whatsthis   : no args => just print tapeid
+# whatsthis --list [n] : print tapeid then list archive n (if n omitted,
+# 0 is assumed.) Note that archives are numbered from zero!
+
+sub rewind();
+sub stopandsay(@);
+
+$etc='/etc/chiark-backup';
+require "$etc/settings.pl";
+require 'backuplib.pl';
+
+$| = 1;
+
+# This isn't intended to be run automatically, so don't bother 
+# with setting status.
+
+# If we are run with the argument --list then list the backup to 
+# stdout. Otherwise just print the tape ID.
+$listing = 0;  # default : don't list
+$listing = 1 if ($ARGV[0] eq '--list');
+$listarchive = 0;
+$listarchive = $ARGV[1] if defined $ARGV[1];
+
+print "Trying to read tape ID from currently inserted tape...\n";
+
+unlink 'TAPEID';
+system "mt -f $ntape setblk $blocksizebytes"; $? and die $?;
+system "dd if=$ntape bs=${blocksize}b count=10 | tar -b$blocksize -vvxf - TAPEID";
+$? and stopandsay "Failed to read TAPEID.\n";
+
+if (!open(T, "TAPEID"))
+{
+  stopandsay "Tape has no ID label.\n";
+}
+
+# OK, there's a TAPEID file, read the ID and check for sanity.
+chomp($tapeid= <T>);
+if ($tapeid =~ m/[^0-9a-zA-Z]/)
+{
+   stopandsay "Tape has a bad (non-alphanumeric) TAPEID ($&).\n";
+}
+elsif (! $tapeid =~ m/[0-9a-zA-Z]/)
+{
+   stopandsay "Empty TAPEID.\n";
+}
+
+print "TAPEID is $tapeid.\n";
+close T;
+
+# If we aren't listing the tape contents, we can just rewind the tape
+# and exit.
+if (!$listing)
+{
+   rewind();
+   exit;
+}
+
+# List the contents of archive $listarchive on the tape.
+# We are already at the right place for the first archive
+# (after the TAPEID). 
+# For any other archive, we skip forwards to the start of that archive.
+if ($listarchive)
+{
+   system "mt -f $ntape fsf $listarchive";
+   $? and stopandsay "Couldn't skip forward to archive $listarchive.";
+}
+
+# Use file to figure out what the archive type is
+# This doesn't seem to work too well, so I'm disabling it -- PMM 
+#$ftype = `dd if=$ntape ibs=$blocksizebytes | file -`;
+#$? and stopandsay "couldn't determine file type: $?";
+$ftype = 'POSIX tar';
+
+# What we want to do here is roughly:
+# dd if=$ntape ibs=$blocksizebytes | readbuffer | afio ???
+#
+# where the afio options are such as to list an archive created with
+# afio -b $softblocksizebytes -Zvo
+
+if ($ftype =~ /POSIX tar/) {
+   # POSIX tar archive; we read it with cpio
+   $reader = "cpio -it -C$softblocksizebytes -Hustar";
+} elsif ($ftype =~ /ASCII cpio/) {
+   $reader = "afio -b $softblocksizebytes -Zt -";
+} elsif ($ftype =~ /dump file/) {
+   stopandsay "sorry: can't list dump files yet";
+} else {
+   stopandsay "listing failed: unknown archive type";
+}
+
+# Now back up so we can read the file again properly
+#system "mt -f $ntape bsf 1"; $? and stopandsay "couldn't backspace tape: $?";
+
+system "dd if=$ntape ibs=$blocksizebytes | readbuffer | $reader";
+$? and stopandsay "listing failed: $?";
+
+# All's well, stop here.
+print "Listing complete.\n";
+rewind();
+exit;
+
+
+# Rewind the tape.
+sub rewind ()
+{
+   system "mt -f $tape rewind"; $? and die $?;
+}
+
+# Print the given message, rewind the tape and exit failure.
+sub stopandsay(@)
+{
+   print @_;
+   rewind();
+   exit(1);
+}
diff --git a/cprogs/Makefile b/cprogs/Makefile
new file mode 100644 (file)
index 0000000..d328bf5
--- /dev/null
@@ -0,0 +1,85 @@
+# Makefile
+# simple make settings
+#
+# This file is part of chiark backup, a system for backing up GNU/Linux and
+# other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+#
+# chiark backup is:
+#  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+#  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+us=    chiark-utils-bin
+
+include ../settings.make
+
+RWBUFFER_SIZE_MB=16
+
+PROGRAMS=              readbuffer writebuffer with-lock-ex xbatmon-simple \
+                       summer watershed rcopy-repeatedly
+SUIDSBINPROGRAMS=      really
+DAEMONS=               trivsoundd
+MAN1PAGES=             readbuffer.1 writebuffer.1 with-lock-ex.1
+MAN8PAGES=             trivsoundd.8 really.8
+BUILTTXTDOCS=          watershed.txt
+TXTDOCS=               $(BUILTTXTDOCS)
+
+TARGETS=       $(PROGRAMS) $(SUIDSBINPROGRAMS) $(DAEMONS) $(BUILTTXTDOCS)
+
+all:           $(TARGETS)
+
+readbuffer:                    readbuffer.o                    rwbuffer.o
+writebuffer:                   writebuffer.o   wrbufcore.o     rwbuffer.o 
+trivsoundd:                    trivsoundd.o    wrbufcore.o     rwbuffer.o 
+really:                                really.o myopt.o
+
+really.o myopt.o rcopy-repeatedly.o: myopt.h
+readbuffer.o writebuffer.o rwbuffer.o wrbufcore.o trivsoundd.o:        rwbuffer.h
+
+xbatmon-simple:        xbatmon-simple.o
+               $(CC) -o $@ $< -L/usr/X11R6/lib -lX11 -lm
+
+summer:                summer.o
+               $(CC) -o $@ $< -lnettle -lgmp
+
+rcopy-repeatedly: rcopy-repeatedly.o myopt.o
+               $(CC) -o $@ $^ -lm -lrt
+
+watershed:     watershed.o
+               $(CC) -o $@ $< -lnettle -lgmp
+
+watershed.txt: watershed.c
+               sed '/^$$/,$$d' <$^ >$@.new && mv -f $@.new $@
+
+install:               all
+               $(INSTALL_DIRECTORY) $(bindir) $(sbindir)
+               $(INSTALL_PROGRAM) $(PROGRAMS) $(bindir)
+               $(INSTALL_PROGRAM) $(DAEMONS) $(sbindir)
+               $(INSTALL) -m 4774 -o root -g $(SYSTEM_GROUP) \
+                       $(SUIDSBINPROGRAMS) $(sbindir)
+
+install-docs:  watershed.txt
+               $(INSTALL_DIRECTORY) $(man1dir) $(man8dir) $(txtdocdir)
+               $(INSTALL) -m 644 $(MAN1PAGES) ${man1dir}/.
+               $(INSTALL) -m 644 $(MAN8PAGES) ${man8dir}/.
+               $(INSTALL) -m 644 $(TXTDOCS) ${txtdocdir}/.
+
+install-examples:
+
+clean:
+               rm -f *~ ./#*# *.o
+
+distclean realclean:   clean
+               rm -f $(TARGETS)
diff --git a/cprogs/dlist.h b/cprogs/dlist.h
new file mode 100644 (file)
index 0000000..5c11e4d
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * dlist.h
+ * - macros for handling doubly linked lists
+ */
+/*
+ *  This file is
+ *    Copyright (C) 1997-1999 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ *  It is part of adns, which is
+ *    Copyright (C) 1997-2000 Ian Jackson <ian@davenant.greenend.org.uk>
+ *    Copyright (C) 1999 Tony Finch <dot@dotat.at>
+ *  
+ *  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 3, 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, consult the Free Software Foundation,
+ *  Inc., website at www.fsf.org, or the GNU Project website at www.gnu.org. 
+ */
+
+#ifndef ADNS_DLIST_H_INCLUDED
+#define ADNS_DLIST_H_INCLUDED
+
+#define LIST_INIT(list) ((list).head= (list).tail= 0)
+#define LINK_INIT(link) ((link).next= (link).back= 0)
+
+#define LIST_UNLINK_PART(list,node,part) \
+  do { \
+    if ((node)->part back) (node)->part back->part next= (node)->part next; \
+      else                                  (list).head= (node)->part next; \
+    if ((node)->part next) (node)->part next->part back= (node)->part back; \
+      else                                  (list).tail= (node)->part back; \
+  } while(0)
+
+#define LIST_LINK_TAIL_PART(list,node,part) \
+  do { \
+    (node)->part next= 0; \
+    (node)->part back= (list).tail; \
+    if ((list).tail) (list).tail->part next= (node); else (list).head= (node); \
+    (list).tail= (node); \
+  } while(0)
+
+#define LIST_UNLINK(list,node) LIST_UNLINK_PART(list,node,)
+#define LIST_LINK_TAIL(list,node) LIST_LINK_TAIL_PART(list,node,)
+
+#endif
diff --git a/cprogs/mcastsoundd.c b/cprogs/mcastsoundd.c
new file mode 100644 (file)
index 0000000..8a8c670
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * http://www.ibiblio.org/pub/Linux/docs/HOWTO/Multicast-HOWTO
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <endian.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "myopt.h"
+
+typedef unsigned char Byte;
+
+static int ov_mode= 'r';
+static const char *ov_requ=  "127.0.0.1";
+static const char *ov_mcast= "239.193.27.221";
+
+static int ov_port_requ= 4101;
+static int ov_port_ctrl= 4101;
+static int ov_port_data= -1;
+
+static const struct cmdinfo cmdinfos[]= {
+  { "server",     0, &ov_mode,0,0, 's' },
+  { "player",     0, &ov_mode,0,0, 'p' },
+  { "request",    0, &ov_mode,0,0, 'r' },
+  { "mcast-addr", 1, 0,&ov_mcast },
+  { "requ-addr",  1, 0,&ov_requ },
+  { "requ-port",  1, &ov_port_requ },
+  { "ctrl-port",  1, &ov_port_ctrl },
+  { "data-port",  1, &ov_port_data },
+  0
+};
+
+
+static int mcast_fd, requ_fd;
+static struct sockaddr_in requ_sa, ctrl_sa, data_sa;
+
+static void sysfail(const char *m) { perror(m); exit(16); }
+
+static Byte packet[1024];
+static int packet_len;
+
+/*---------- marshalling ----------*/
+
+static uint64_t htonll(uint64_t v) {
+#if LITTLE_ENDIAN
+  return (v >> 32) | (v << 32);
+#endif
+#if BIG_ENDIAN
+  return v;
+#endif
+}
+
+#define OP_CTRL_PLAY 1
+#define OP_CTRL_STOP 2
+#define OP_CTRL_DATA 3
+
+#define MAR_CTRL_PLAY                          \
+  FI8(operation)                               \
+  FI8(reserved)                                        \
+  FI8(generation)                              \
+  FI8(counter)                                 \
+  FI64(totallen)                               \
+  FI64(startts)                                        \
+  FI32(starttns)                               \
+  FI32(txrate)                                 \
+  FR(trackfn,char,256)
+  
+#define MAR_CTRL_STOP                          \
+  FI8(operation)                               \
+  FI8(reserved)                                        \
+  FR0
+
+#define MAR_DATA                               \
+  FI8(operation)                               \
+  FI8(reserved)                                        \
+  FI8(generation)                              \
+  FI8(counter)                                 \
+  FI64(offset)                                 \
+  FR(data,Byte,1024)
+     
+#define FI8(f)      F(f, uint8_t,  v)
+#define FI32(f)     F(f, uint32_t, htonl(v))
+#define FI64(f)     F(f, uint64_t, htonll(v))
+
+#define MARS                                   \
+  MAR(CTRL_PLAY)                               \
+  MAR(CTRL_STOP)                               \
+  MAR(DATA)
+
+#define F(f,t,c) t f;
+#define FR(f,t,l) t f[(l)]; int f##_l;
+#define FR0 /* */
+#define MAR(m) typedef struct Mar_##m { MAR_##m } Mar_##m;
+MARS
+#undef F
+#undef FR
+#undef FR0
+#undef MAR
+
+#define F(f,t,c) { t v= d->f; *(t*)p= c; p += sizeof(t); };
+#define FR(f,t,l) assert(d->f##_l<=l); memcpy(p,d->f,d->f##_l); p+=d->f##_l;
+#define FR0 /* */
+#define MAR(m)                                 \
+  static void mar_##m(const Mar_##m *d) {      \
+    Byte *p= packet;                           \
+    MAR_##m                                    \
+    packet_len= p - packet;                    \
+    assert(packet_len < sizeof(packet));       \
+  }
+MARS
+#undef F
+#undef FR
+#undef FR0
+#undef MAR
+
+#define F(f,t,c) {                             \
+    t v;                                       \
+    if (lr < sizeof(t)) return -1;             \
+    v= *(const t*)p;                           \
+    p += sizeof(t);  lr -= sizeof(t);          \
+    d->f= c;                                   \
+  };
+#define FR(f,t,l) {                            \
+    if (lr > l) return -1;                     \
+    memcpy(d->f, p, lr);                       \
+    d->f##_l= lr;                              \
+  };
+#define FR0                                    \
+    if (lr) return -1;
+#define MAR(m)                                 \
+  static int unmar_##m(Mar_##m *d) {           \
+    const Byte *p= packet;                     \
+    int lr= packet_len;                                \
+    MAR_##m                                    \
+    return 0;                                  \
+  }
+MARS
+#undef F
+#undef FR
+#undef FR0
+#undef MAR
+
+/*---------- general stuff ----------*/
+
+static void nonblock(int fd) {
+  int r;
+  r= fcntl(fd,F_GETFL);  if (r<0) sysfail("nonblock fcntl F_GETFL");
+  r |= O_NONBLOCK;
+  r= fcntl(fd,F_SETFL,r);  if (r<0) sysfail("nonblock fcntl F_GETFL");
+}
+
+static void blocksignals(int how) {
+  sigset_t set;
+  int r;
+
+  sigemptyset(&set);
+  sigaddset(&set,SIGCHLD);
+  r= sigprocmask(how,&set,0);
+  if (r) sysfail("sigprocmask");
+}
+
+static int mksocket(int type, int proto,
+                   const struct sockaddr_in *sa, const char *what) {
+  int fd, r;
+
+  fd= socket(PF_INET, type, proto);
+  if (fd<0) sysfail("socket %s",what);
+
+  r= bind(fd, (struct sockaddr*)&mcast_sa, sizeof(*sa));
+  if (r) sysfail("bind %s",what);
+
+  return fd;
+}
+
+static void mkmcastrecv(const struct sockaddr_in *sa, const char *what) {
+  struct ip_mreq mreq;
+  int r;
+
+  mcast_fd= mksocket(SOCK_DGRAM, IPPROTO_UDP, sa, what);
+
+  mreq.imr_multiaddr= sa->sin_addr;
+  mreq.imr_interface.s_addr= INADDR_ANY;
+  r= setsockopt(mcast_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+  if (r) sysfail("add mcast membership %s", what);
+}
+
+/*---------- player ----------*/
+
+static void recvd_play(void) {
+  Mar_CTRL_PLAY pkt;
+  int r;
+
+  r= unmar_CTRL_PLAY(&pkt);
+  if (r) { fprintf(stderr,"bad PLAY packet\n"); return; }
+  
+}
+
+static void recvd_stop(void) {
+  Mar_CTRL_STOP pkt;
+  int r;
+
+  r= unmar_CTRL_STOP(&pkt);
+  if (r) { fprintf(stderr,"bad STOP packet\n"); return; }
+}
+  
+static void player(void) {
+  struct sockaddr_in peer_sa, old_peer_sa;
+  socklen_t peer_salen;
+  int r;
+
+  mkmcastrecv(&ctrl_sa, "ctrl");
+  
+  memset(&old_peer_sa, 0, sizeof(old_peer_sa));
+
+  for (;;) {
+    peer_salen= sizeof(peer_sa);
+    memset(&peer_sa, 0, sizeof(peer_sa));
+    
+    blocksignals(SIG_UNBLOCK);
+    packet_len= recvfrom(mcast_fd, packet, sizeof(packet),
+                        MSG_TRUNC, (struct sockaddr*)&peer_sa, &peer_salen);
+    blocksignals(SIG_BLOCK);
+
+    if (packet_len<0) {
+      if (errno==EINTR) continue;
+      perror("mcast_fd recvfrom");
+      continue;
+    }
+    if (peer_salen != sizeof(peer_sa)) {
+      fprintf(stderr,"mcast_fd recvfrom salen %ld not %ld\n",
+             (unsigned long)peer_salen, (unsigned long)sizeof(peer_sa));
+      continue;
+    }
+    if (packet_len > sizeof(packet)) {
+      fprintf(stderr,"mcast_fd recvfrom packet len %ld longer than max %ld\n",
+             (unsigned long)packet_len, (unsigned long)sizeof(packet));
+      continue;
+    }
+    if (memcmp(&old_peer_sa, &peer_sa, sizeof(old_peer_sa))) {
+      char *p= inet_ntoa(peer_sa.sin_addr);
+      fprintf(stderr,"receiving from %s:%d\n",p,ntohs(peer_sa.sin_port));
+      memcpy(&old_peer_sa, &peer_sa, sizeof(old_peer_sa));
+    }
+    if (packet_len==0) {
+      fprintf(stderr,"empty packet!\n");
+      continue;
+    }
+    switch (packet[0]) {
+    case OP_CTRL_PLAY:
+      recvd_play();
+      break;
+    case OP_CTRL_STOP:
+      recvd_stop();
+      break;
+    default:
+      fprintf(stderr,"unknown opcode %d\n",packet[0]);
+    }
+  }
+}
+
+/*---------- server ----------*/
+
+void server(void) {
+  requ_fd= mksocket(SOCK_STREAM, IPPROTO_TCP, &requ_sa, "requ");
+  
+
+/*---------- main ----------*/
+
+static void argaddr(struct sin_addr *sa, const char *addr_name, int port) {
+  memset(sa,0,sizeof(*sa));
+  sa->sin_family= AF_INET;
+  
+  r= inet_aton(ov_mcast, &mcast_sa.sin_addr);
+  if (!r) badusage("invalid addr `%s'", addr_name);
+
+  if (port<0 || port>65536) badusage("invalid port %d",port);
+
+  sa->sin_port= htons(port);
+}
+
+int main(int argc, const char **argv) {
+  int r;
+
+  if (ov_port_data < 0) ov_port_data= ov_port_ctrl+1;
+  myopt(&argv, cmdinfos);
+
+  argaddr(&requ_sa, ov_requ,  ov_requ_port);
+  argaddr(&ctrl_sa, ov_mcast, ov_ctrl_port);
+  argaddr(&data_sa, ov_data,  ov_data_port);
+
+  if (argv[1] && ov_mode != 'p')
+    badusage("mode takes no non-option arguments");
+
+  switch (ov_mode) {
+  case 'p':
+    player();
+    break;
+  case 's':
+    server();
+    break;
+  case 'r':
+    if (!argv[1] || argv[2])
+      badusage("play-requester takes one non-option argument");
+    request(argv[1]);
+    break;
+  default:
+    abort();
+  }
+
+  nonblock(0);
+  mar_CTRL_PLAY(0);
+  mar_CTRL_STOP(0);
+  mar_DATA(0);
+  unmar_DATA(0);
+  return 0;
+}
diff --git a/cprogs/myopt.c b/cprogs/myopt.c
new file mode 100644 (file)
index 0000000..c4d5a86
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * myopt.c - my very own option parsing
+ *
+ * Copyright (C) 1994,1995 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "myopt.h"
+
+void badusage(const char *fmt, ...) {
+  va_list al;
+  
+  va_start(al,fmt);
+  vfprintf(stderr,fmt,al);
+  va_end(al);
+  fputc('\n',stderr);
+  usagemessage();
+  exit(-1);
+}
+
+void myopt(const char *const **argvp, const struct cmdinfo *cmdinfos) {
+  const struct cmdinfo *cip;
+  const char *p, *value;
+  int l;
+
+  ++(*argvp);
+  while ((p= **argvp) && *p == '-') {
+    ++(*argvp);
+    if (!strcmp(p,"--")) break;
+    if (*++p == '-') {
+      ++p; value=0;
+      for (cip= cmdinfos;
+           cip->olong || cip->oshort;
+           cip++) {
+        if (!cip->olong) continue;
+        if (!strcmp(p,cip->olong)) break;
+        l= strlen(cip->olong);
+        if (!strncmp(p,cip->olong,l) &&
+            (p[l]== ((cip->takesvalue==2) ? '-' : '='))) { value=p+l+1; break; }
+      }
+      if (!cip->olong) badusage("unknown option --%s",p);
+      if (cip->takesvalue) {
+        if (!value) {
+          value= *(*argvp)++;
+          if (!value) badusage("--%s option takes a value",cip->olong);
+        }
+        if (cip->call) cip->call(cip,value);
+        else *cip->sassignto= value;
+      } else {
+        if (value) badusage("--%s option does not take a value",cip->olong);
+        if (cip->call) cip->call(cip,0);
+        else *cip->iassignto= cip->arg;
+      }
+    } else {
+      while (*p) {
+        for (cip= cmdinfos; (cip->olong || cip->oshort) && *p != cip->oshort; cip++);
+        if (!cip->oshort) badusage("unknown option -%c",*p);
+        p++;
+        if (cip->takesvalue) {
+          if (!*p) {
+            value= *(*argvp)++;
+            if (!value) badusage("-%c option takes a value",cip->oshort);
+          } else {
+            value= p; p="";
+            if (*value == '=') value++;
+          }
+          if (cip->call) cip->call(cip,value);
+          else *cip->sassignto= value;
+        } else {
+          if (*p == '=') badusage("-%c option does not take a value",cip->oshort);
+          if (cip->call) cip->call(cip,0);
+          else *cip->iassignto= cip->arg;
+        }
+      }
+    }
+  }
+}
diff --git a/cprogs/myopt.h b/cprogs/myopt.h
new file mode 100644 (file)
index 0000000..664fcca
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * myopt.h - declarations for my very own option parsing
+ *
+ * Copyright (C) 1994,1995 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
+
+#ifndef MYOPT_H
+#define MYOPT_H
+
+extern void usagemessage(void); /* supply this */
+
+typedef void (*voidfnp)(void);
+
+struct cmdinfo {
+  const char *olong;
+  char oshort;
+  int takesvalue; /* 0 = normal   1 = standard value   2 = option string cont */
+  int *iassignto;
+  const char **sassignto;
+  void (*call)(const struct cmdinfo*, const char *value);
+  int arg;
+  void *parg;
+  voidfnp farg;
+};
+
+void myopt(const char *const **argvp, const struct cmdinfo *cmdinfos);
+void badusage(const char *fmt, ...);
+
+#endif /* MYOPT_H */
diff --git a/cprogs/rcopy-repeatedly.c b/cprogs/rcopy-repeatedly.c
new file mode 100644 (file)
index 0000000..fc2741d
--- /dev/null
@@ -0,0 +1,722 @@
+/*
+ * rcopy-repeatedly
+ *
+ *   You say  rcopy-repeatedly local-file user@host:remote-file
+ *   and it polls for changes to local-file and copies them to
+ *   remote-file.  rcopy-repeatedly must be installed at the far end.
+ *   You can copy in either direction but not between two remote
+ *   locations.
+ *
+ *   Limitations:
+ *    * Cannot cope with files which are modified between us opening
+ *      and statting them for the first time; if the file shrinks
+ *      we may bomb out.  Workaround: use rename-in-place.
+ *    * When transferring large files, bandwidth limiter will
+ *      be `lumpy' as the whole file is transferred and then we
+ *      sleep.
+ *    * Cannot copy between two local files.  Workaround: a symlink
+ *      (but presumably there was some reason you weren't doing that)
+ *    * No ability to synchronise more than just exactly one file
+ *    * Polls.  It would be nice to use inotify or something.
+ *
+ *   Inherent limitations:
+ *    * Can only copy plain files.
+ *
+ *   See the --help for options.
+ */     
+
+/*
+ * rcopy-repeatedly is
+ *  Copyright (C) 2008 Ian Jackson <ian@davenant.greenend.org.uk>
+ * and the option parser we use is
+ *  Copyright (C) 1994,1995 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
+
+/*
+ * protocol is:
+ *   server sends banner
+ *    - "#rcopy-repeatedly#\n"
+ *    - length of declaration, as 4 hex digits, zero prefixed,
+ *      and a newline [5 bytes].  In this protocol version this
+ *      will be "0002" but client _must_ parse it.
+ *   server sends declaration
+ *    - one of "u " or "d" [1 byte]
+ *    - optionally, some more ascii text, reserved for future use
+ *      must be ignored by declaree (but not sent by declarer)
+ *    - a newline [1 byte]
+ *   client sends
+ *    - 0x02   START
+ *        n    2 bytes big endian declaration length
+ *        ...  client's declaration (ascii text, including newline)
+ 8             see above
+ * then for each update
+ *   sender sends one of
+ *    - 0x03   destination file should be deleted
+ *             but note that contents must be retained by receiver
+ *             as it may be used for rle updates
+ *    - 0x04   complete new destination file follows, 64-bit length
+ *        l    8 bytes big endian length
+ *        ...  l bytes data
+ *             receiver must then reply with 0x01 ACK
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <time.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+#include <math.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "myopt.h"
+
+#define REPLMSG_ACK    0x01
+#define REPLMSG_START  0x02
+#define REPLMSG_RM     0x03
+#define REPLMSG_FILE64 0x04
+
+static const char banner[]= "#rcopy-repeatedly#\n";
+
+static FILE *commsi, *commso;
+
+static double max_bw_prop= 0.2;
+static int txblocksz= INT_MAX, verbose=1;
+static int min_interval_usec= 100000; /* 100ms */
+
+static int nsargs;
+static const char **sargs;
+
+static const char *rsh_program= 0;
+static const char *rcopy_repeatedly_program= "rcopy-repeatedly";
+static int server_upcopy=-1; /* -1 means not yet known; 0 means download */
+  /* `up' means towards the client,
+   * since we regard the subprocess as `down' */
+
+static int udchar;
+
+static char mainbuf[65536]; /* must be at least 2^16 */
+
+#define NORETURN __attribute__((noreturn))
+
+static void vdie(int ec, const char *pfx, int eno,
+                const char *fmt, va_list al) NORETURN;
+static void vdie(int ec, const char *pfx, int eno,
+                const char *fmt, va_list al) {
+  fputs("rcopy-repeatedly: ",stderr);
+  if (server_upcopy>=0) fputs("server: ",stderr);
+  if (pfx) fprintf(stderr,"%s: ",pfx);
+  vfprintf(stderr,fmt,al);
+  if (eno!=-1) fprintf(stderr,": %s",strerror(eno));
+  fputc('\n',stderr);
+  exit(ec);
+}
+static void die(int ec, const char *pfx, int eno,
+               const char *fmt, ...) NORETURN;
+static void die(int ec, const char *pfx, int eno,
+               const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  vdie(ec,pfx,eno,fmt,al);
+}
+
+static void diem(void) NORETURN;
+static void diem(void) { die(16,0,errno,"malloc failed"); }
+static void *xmalloc(size_t sz) {
+  assert(sz);
+  void *p= malloc(sz);
+  if (!p) diem();
+  return p;
+}
+static void *xrealloc(void *p, size_t sz) {
+  assert(sz);
+  p= realloc(p,sz);
+  if (!p) diem();
+  return p;
+}
+
+static void diee(const char *fmt, ...) NORETURN;
+static void diee(const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  vdie(12,0,errno,fmt,al);
+}
+static void die_protocol(const char *fmt, ...) NORETURN;
+static void die_protocol(const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  vdie(10,"protocol error",-1,fmt,al);
+}
+
+static void die_badrecv(const char *what) NORETURN;
+static void die_badrecv(const char *what) {
+  if (ferror(commsi)) diee("communication failed while receiving %s", what);
+  if (feof(commsi)) die_protocol("receiver got unexpected EOF in %s", what);
+  abort();
+}
+static void die_badsend(void) NORETURN;
+static void die_badsend(void) {
+  diee("transmission failed");
+}
+
+static void send_flush(void) {
+  if (ferror(commso) || fflush(commso))
+    die_badsend();
+}
+static void sendbyte(int c) {
+  if (putc(c,commso)==EOF)
+    die_badsend();
+}
+
+static void mfreadcommsi(void *buf, int l, const char *what) {
+  int r= fread(buf,1,l,commsi);  if (r!=l) die_badrecv(what);
+}
+static void mfwritecommso(const void *buf, int l) {
+  int r= fwrite(buf,1,l,commso);  if (r!=l) die_badsend();
+}
+
+static void mpipe(int p[2]) { if (pipe(p)) diee("could not create pipe"); }
+static void mdup2(int fd, int fd2) {
+  if (dup2(fd,fd2)!=fd2) diee("could not dup2(%d,%d)",fd,fd2);
+}
+
+typedef void copyfile_die_fn(FILE *f, const char *xi);
+
+struct timespec ts_sendstart;
+
+static void mgettime(struct timespec *ts) {
+  int r= clock_gettime(CLOCK_MONOTONIC, ts);
+  if (r) diee("clock_gettime failed");
+}
+
+static void bandlimit_sendstart(void) {
+  mgettime(&ts_sendstart);
+}
+
+static double mgettime_elapsed(struct timespec ts_base,
+                              struct timespec *ts_ret) {
+  mgettime(ts_ret);
+  return (ts_ret->tv_sec - ts_base.tv_sec) +
+         (ts_ret->tv_nsec - ts_base.tv_nsec)*1e-9;
+}
+
+static void flushstderr(void) {
+  if (ferror(stderr) || fflush(stderr))
+    diee("could not write progress to stderr");
+}
+
+static void verbosespinprintf(const char *fmt, ...) {
+  static const char spinnerchars[]= "/-\\";
+  static int spinnerchar;
+
+  if (!verbose)
+    return;
+
+  va_list al;
+  va_start(al,fmt);
+  fprintf(stderr,"      %c ",spinnerchars[spinnerchar]);
+  spinnerchar++; spinnerchar %= sizeof(spinnerchars)-1;
+  vfprintf(stderr,fmt,al);
+  putc('\r',stderr);
+  flushstderr();
+}
+
+static void bandlimit_sendend(uint64_t bytes, int *interval_usec_update) {
+  struct timespec ts_buf;
+  double elapsed= mgettime_elapsed(ts_sendstart, &ts_buf);
+  double secsperbyte_observed= elapsed / bytes;
+
+  double min_update= elapsed / max_bw_prop;
+  if (min_update > 1e3) min_update= 1e3;
+  int min_update_usec= min_update * 1e6;
+
+  if (*interval_usec_update < min_update_usec)
+    *interval_usec_update= min_update_usec;
+
+  verbosespinprintf("%12lluby %10.3fs %13.2fkby/s %8dms",
+                   (unsigned long long)bytes, elapsed,
+                   1e-3/secsperbyte_observed, *interval_usec_update/1000);
+}
+static void copyfile(FILE *sf, copyfile_die_fn *sdie, const char *sxi,
+                    FILE *df, copyfile_die_fn *ddie, const char *dxi,
+                    uint64_t lstart, int amsender) {
+  struct timespec ts_last;
+  int now, r;
+  uint64_t l=lstart, done=0;
+
+  ts_last= ts_sendstart;
+
+  while (l>0) {
+    now= l < sizeof(mainbuf) ? l : sizeof(mainbuf);
+    if (now > txblocksz) now= txblocksz;
+
+    r= fread(mainbuf,1,now,sf);  if (r!=now) sdie(sf,sxi);
+    r= fwrite(mainbuf,1,now,df);  if (r!=now) ddie(df,dxi);
+    l -= now;
+    done += now;
+
+    if (verbose) {
+      fprintf(stderr," %3d%% \r",
+             (int)(done*100.0/lstart));
+      flushstderr();
+    }
+  }
+}
+
+static void copydie_inputfile(FILE *f, const char *filename) {
+  diee("read failed on source file `%s'", filename);
+}
+static void copydie_tmpwrite(FILE *f, const char *tmpfilename) {
+  diee("write failed to temporary receiving file `%s'", tmpfilename);
+}
+static void copydie_commsi(FILE *f, const char *what) {
+  die_badrecv(what);
+}
+static void copydie_commso(FILE *f, const char *what) {
+  die_badsend();
+}
+  
+static int generate_declaration(void) {
+  /* returns length; declaration is left in mainbuf */
+  char *p= mainbuf;
+  *p++= udchar;
+  *p++= '\n';
+  return p - mainbuf;
+}
+
+static void read_declaration(int decllen) {
+  assert(decllen <= sizeof(mainbuf));
+  if (decllen<2) die_protocol("declaration too short");
+  mfreadcommsi(mainbuf,decllen,"declaration");
+  if (mainbuf[decllen-1] != '\n')
+    die_protocol("declaration missing final newline");
+  if (mainbuf[0] != udchar)
+    die_protocol("declaration incorrect direction indicator");
+}
+
+static void receiver(const char *filename) {
+  FILE *newfile;
+  char *tmpfilename;
+  int r, c;
+
+  char *lastslash= strrchr(filename,'/');
+  if (!lastslash)
+    r= asprintf(&tmpfilename, ".rcopy-repeatedly.#%s#", filename);
+  else
+    r= asprintf(&tmpfilename, "%.*s/.rcopy-repeatedly.#%s#",
+               (int)(lastslash-filename), filename, lastslash+1);
+  if (r==-1) diem();
+  
+  r= unlink(tmpfilename);
+  if (r && errno!=ENOENT)
+    diee("could not remove temporary receiving file `%s'", tmpfilename);
+  
+  for (;;) {
+    send_flush();
+    c= fgetc(commsi);
+
+    switch (c) {
+
+    case EOF:
+      if (ferror(commsi)) die_badrecv("transfer message code");
+      assert(feof(commsi));
+      return;
+
+    case REPLMSG_RM:
+      r= unlink(filename);
+      if (r && errno!=ENOENT)
+       diee("source file removed but could not remove destination file `%s'",
+            filename);
+      break;
+      
+    case REPLMSG_FILE64:
+      newfile= fopen(tmpfilename, "wb");
+      if (!newfile) diee("could not create temporary receiving file `%s'",
+                        tmpfilename);
+      uint8_t lbuf[8];
+      mfreadcommsi(lbuf,8,"FILE64 l");
+
+      uint64_t l=
+       (lbuf[0] << 28 << 28) |
+       (lbuf[1] << 24 << 24) |
+       (lbuf[2] << 16 << 24) |
+       (lbuf[3] <<  8 << 24) |
+       (lbuf[4]       << 24) |
+       (lbuf[5]       << 16) |
+       (lbuf[6]       <<  8) |
+       (lbuf[7]            ) ;
+
+      copyfile(commsi, copydie_commsi,"FILE64 file data",
+              newfile, copydie_tmpwrite,tmpfilename,
+              l, 0);
+
+      if (fclose(newfile)) diee("could not flush and close temporary"
+                               " receiving file `%s'", tmpfilename);
+      if (rename(tmpfilename, filename))
+       diee("could not install new version of destination file `%s'",
+            filename);
+
+      sendbyte(REPLMSG_ACK);
+      break;
+
+    default:
+      die_protocol("unknown transfer message code 0x%02x",c);
+
+    }
+  }
+}
+
+static void sender(const char *filename) {
+  FILE *f, *fold;
+  int interval_usec, r, c;
+  struct stat stabtest, stab;
+  enum { told_nothing, told_file, told_remove } told;
+
+  interval_usec= 0;
+  fold= 0;
+  told= told_nothing;
+  
+  for (;;) {
+    if (interval_usec) {
+      send_flush();
+      usleep(interval_usec);
+    }
+    interval_usec= min_interval_usec;
+
+    r= stat(filename, &stabtest);
+    if (r) {
+      f= 0;
+    } else {
+      if (told == told_file &&
+         stabtest.st_mode  == stab.st_mode  &&
+         stabtest.st_dev   == stab.st_dev   &&
+         stabtest.st_ino   == stab.st_ino   &&
+         stabtest.st_mtime == stab.st_mtime &&
+         stabtest.st_size  == stab.st_size)
+       continue;
+      f= fopen(filename, "rb");
+    }
+    
+    if (!f) {
+      if (errno!=ENOENT) diee("could not access source file `%s'",filename);
+      if (told != told_remove) {
+       verbosespinprintf
+         (" ENOENT                                                    ");
+       sendbyte(REPLMSG_RM);
+       told= told_remove;
+      }
+      continue;
+    }
+
+    if (fold) fclose(fold);
+    fold= 0;
+
+    r= fstat(fileno(f),&stab);
+    if (r) diee("could not fstat source file `%s'",filename);
+
+    if (!S_ISREG(stab.st_mode))
+      die(8,0,-1,"source file `%s' is not a plain file",filename);
+
+    uint8_t hbuf[9]= {
+      REPLMSG_FILE64,
+      stab.st_size >> 28 >> 28,
+      stab.st_size >> 24 >> 24,
+      stab.st_size >> 16 >> 24,
+      stab.st_size >>  8 >> 24,
+      stab.st_size       >> 24,
+      stab.st_size       >> 16,
+      stab.st_size       >>  8,
+      stab.st_size
+    };
+
+    bandlimit_sendstart();
+
+    mfwritecommso(hbuf,9);
+
+    copyfile(f, copydie_inputfile,filename,
+            commso, copydie_commso,0,
+            stab.st_size, 1);
+
+    send_flush();
+
+    c= fgetc(commsi);  if (c==EOF) die_badrecv("ack");
+    if (c!=REPLMSG_ACK) die_protocol("got %#02x instead of ACK",c);
+
+    bandlimit_sendend(stab.st_size, &interval_usec);
+
+    fold= f;
+    told= told_file;
+  }
+}
+
+typedef struct {
+  const char *userhost, *path;
+} FileSpecification;
+
+static FileSpecification srcspec, dstspec;
+
+static void of__server(const struct cmdinfo *ci, const char *val) {
+  int ncount= nsargs + 1 + !!val;
+  sargs= xrealloc(sargs, sizeof(*sargs) * ncount);
+  sargs[nsargs++]= ci->olong;
+  if (val)
+    sargs[nsargs++]= val;
+}
+
+static int of__server_int(const struct cmdinfo *ci, const char *val) {
+  of__server(ci,val);
+  long v;
+  char *ep;
+  errno= 0; v= strtol(val,&ep,10);
+  if (!*val || *ep || errno || v<INT_MIN || v>INT_MAX)
+    badusage("bad integer argument `%s' for --%s",val,ci->olong);
+  return v;
+}
+
+static void of_help(const struct cmdinfo *ci, const char *val) {
+  usagemessage();
+  if (ferror(stdout)) diee("could not write usage message to stdout");
+  exit(0);
+}
+
+static void of_bw(const struct cmdinfo *ci, const char *val) {
+  int pct= of__server_int(ci,val);
+  if (pct<1 || pct>100)
+    badusage("bandwidth percentage must be between 1 and 100 inclusive");
+  *(double*)ci->parg= pct * 0.01;
+}
+
+static void of_server_int(const struct cmdinfo *ci, const char *val) {
+  *(int*)ci->parg= of__server_int(ci,val);
+}
+
+void usagemessage(void) {
+  printf(
+        "usage: rcopy-repeatedly [<options>] <file> <file>\n"
+        "  <file> may be <local-file> or [<user>@]<host>:<file>\n"
+        "  exactly one of each of the two forms must be provided\n"
+        "  a file is taken as remote if it has a : before the first /\n"
+        "general options:\n"
+        "  --help\n"
+        "  --quiet | -q\n"
+        "options for bandwidth (and cpu time) control:\n"
+        "  --max-bandwidth-percent  (default %d)\n"
+        "  --tx-block-size      (default/max %d)\n"
+        "  --min-interval-usec  (default %d)\n"
+        "options for finding programs:\n"
+        "  --rcopy-repeatedly  (default: rcopy-repeatedly)\n"
+        "  --rsh-program       (default: $RCOPY_REPEATEDLY_RSH or $RSYNC_RSH or ssh)\n"
+        "options passed to server side via ssh:\n"
+        "  --receiver --sender, bandwidth control options\n",
+         (int)(max_bw_prop*100), (int)sizeof(mainbuf), min_interval_usec);
+}
+
+static const struct cmdinfo cmdinfos[]= {
+  { "help",     .call= of_help },
+  { "max-bandwidth-percent", 0,1,.call=of_bw,.parg=&max_bw_prop            },
+  { "tx-block-size",0,     1,.call=of_server_int, .parg=&txblocksz         },
+  { "min-interval-usec",0, 1,.call=of_server_int, .parg=&min_interval_usec },
+  { "rcopy-repeatedly",0,  1, .sassignto=&rcopy_repeatedly_program         },
+  { "rsh-program",0,       1, .sassignto=&rsh_program                      },
+  { "quiet",'q',  .iassignto= &verbose,       .arg=0                       },
+  { "receiver",   .iassignto= &server_upcopy, .arg=0                       },
+  { "sender",     .iassignto= &server_upcopy, .arg=1                       },
+  { 0 }
+};
+
+static void server(const char *filename) {
+  int c, l;
+  char buf[2];
+
+  udchar= server_upcopy?'u':'d';
+
+  commsi= stdin;
+  commso= stdout;
+  l= generate_declaration();
+  fprintf(commso, "%s%04x\n", banner, l);
+  mfwritecommso(mainbuf, l);
+  send_flush();
+
+  c= fgetc(commsi);
+  if (c==EOF) {
+    if (feof(commsi)) exit(14);
+    assert(ferror(commsi));  die_badrecv("initial START message");
+  }
+  if (c!=REPLMSG_START) die_protocol("initial START was %#02x instead",c);
+
+  mfreadcommsi(buf,2,"START l");
+  l= (buf[0] << 8) | buf[1];
+
+  read_declaration(l);
+
+  if (server_upcopy)
+    sender(filename);
+  else
+    receiver(filename);
+}
+
+static void client(void) {
+  int uppipe[2], downpipe[2], r;
+  pid_t child;
+  FileSpecification *remotespec;
+  const char *remotemode;
+
+  mpipe(uppipe);
+  mpipe(downpipe);
+
+  if (srcspec.userhost) {
+    udchar= 'u';
+    remotespec= &srcspec;
+    remotemode= "--sender";
+  } else {
+    udchar= 'd';
+    remotespec= &dstspec;
+    remotemode= "--receiver";
+  }
+
+  sargs= xrealloc(sargs, sizeof(*sargs) * (7 + nsargs));
+  memmove(sargs+5, sargs, sizeof(*sargs) * nsargs);
+  sargs[0]= rsh_program;
+  sargs[1]= remotespec->userhost;
+  sargs[2]= rcopy_repeatedly_program;
+  sargs[3]= remotemode;
+  sargs[4]= "--";
+  sargs[5+nsargs]= remotespec->path;
+  sargs[6+nsargs]= 0;
+    
+  child= fork();
+  if (child==-1) diee("fork failed");
+  if (!child) {
+    mdup2(downpipe[0],0);
+    mdup2(uppipe[1],1);
+    close(uppipe[0]); close(downpipe[0]);
+    close(uppipe[1]); close(downpipe[1]);
+
+    execvp(rsh_program, (char**)sargs);
+    diee("failed to execute rsh program `%s'",rsh_program);
+  }
+
+  commso= fdopen(downpipe[1],"wb");
+  commsi= fdopen(uppipe[0],"rb");
+  if (!commso || !commsi) diee("fdopen failed");
+  close(downpipe[0]);
+  close(uppipe[1]);
+  
+  char banbuf[sizeof(banner)-1 + 5 + 1];
+  r= fread(banbuf,1,sizeof(banbuf)-1,commsi);
+  if (ferror(commsi)) die_badrecv("read banner");
+
+  if (r!=sizeof(banbuf)-1 ||
+      memcmp(banbuf,banner,sizeof(banner)-1) ||
+      banbuf[sizeof(banner)-1 + 4] != '\n') {
+    const char **sap;
+    int count=0;
+    for (count=0, sap=sargs; *sap; sap++) count+= strlen(*sap)+1;
+    char *cmdline= xmalloc(count+1);
+    cmdline[0]=' ';
+    for (sap=sargs; *sap; sap++) {
+      strcat(cmdline," ");
+      strcat(cmdline,*sap);
+    }
+    
+    die(8,0,-1,"did not receive banner as expected -"
+       " shell dirty? ssh broken?\n"
+       " try running\n"
+       "  %s\n"
+       " and expect the first line to be\n"
+       "  %s",
+       cmdline, banner);
+  }
+  
+  banbuf[sizeof(banbuf)-1]= 0;
+  char *ep;
+  long decllen= strtoul(banbuf + sizeof(banner)-1, &ep, 16);
+  if (ep != banbuf + sizeof(banner)-1 + 4)
+    die_protocol("declaration length syntax error");
+
+  read_declaration(decllen);
+
+  int l= generate_declaration();
+  sendbyte(REPLMSG_START);
+  sendbyte((l >> 8) & 0x0ff);
+  sendbyte( l       & 0x0ff);
+  mfwritecommso(mainbuf,l);
+
+  if (remotespec==&srcspec)
+    receiver(dstspec.path);
+  else
+    sender(srcspec.path);
+}
+
+static void parse_file_specification(FileSpecification *fs, const char *arg,
+                                    const char *what) {
+  const char *colon;
+  
+  if (!arg) badusage("too few arguments - missing %s\n",what);
+
+  for (colon=arg; ; colon++) {
+    if (!*colon || *colon=='/') {
+      fs->userhost=0;
+      fs->path= arg;
+      return;
+    }
+    if (*colon==':') {
+      char *uh= xmalloc(colon-arg + 1);
+      memcpy(uh,arg, colon-arg);  uh[colon-arg]= 0;
+      fs->userhost= uh;
+      fs->path= colon+1;
+      return;
+    }
+  }
+}
+
+int main(int argc, const char *const *argv) {
+  setvbuf(stderr,0,_IOLBF,BUFSIZ);
+
+  myopt(&argv, cmdinfos);
+
+  if (!rsh_program) rsh_program= getenv("RCOPY_REPEATEDLY_RSH");
+  if (!rsh_program) rsh_program= getenv("RSYNC_RSH");
+  if (!rsh_program) rsh_program= "ssh";
+
+  if (txblocksz<1) badusage("transmit block size must be at least 1");
+  if (min_interval_usec<0) badusage("minimum update interval may not be -ve");
+
+  if (server_upcopy>=0) {
+    if (!argv[0] || argv[1])
+      badusage("server mode must have just the filename as non-option arg");
+    server(argv[0]);
+  } else {
+    parse_file_specification(&srcspec, argv[0], "source");
+    parse_file_specification(&dstspec, argv[1], "destination");
+    if (argv[2]) badusage("too many non-option arguments");
+    if (!!srcspec.userhost == !!dstspec.userhost)
+      badusage("need exactly one remote file argument");
+    client();
+  }
+  return 0;
+}
diff --git a/cprogs/readbuffer.1 b/cprogs/readbuffer.1
new file mode 100644 (file)
index 0000000..18f6214
--- /dev/null
@@ -0,0 +1,28 @@
+.TH readbuffer 1 2001-10-21 chiark-backup
+.SH NAME
+readbuffer \- read input from devices which don't like constant stopping and starting
+.SH SYNOPSIS
+.B readbuffer
+.RB [ --mlock ]
+.RI [ size ]
+.SH DESCRIPTION
+.B readbuffer
+reads data on standard input and writes it to standard output.  It
+will internally buffer up to \fIsize\fR megabytes of data, and will
+only read more data when the buffer is at least 75% empty.
+.PP
+\fIsize\fR may also be suffixed with
+.BR m ", " k ", or " b
+to indicate that it is in megabytes (2^20), kilobytes (2^10) or bytes.
+.PP
+It is intended for use in situations where many small
+reads are undesirable for performance reasons, e.g. tape drives.
+.SH OPTIONS
+.TP
+.B --mlock
+Calls
+.BR mlock (2)
+to lock the buffer into memory.
+.SH "SEE ALSO"
+.BR writebuffer (1),
+.BR mlock (2)
diff --git a/cprogs/readbuffer.c b/cprogs/readbuffer.c
new file mode 100644 (file)
index 0000000..00df636
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * readbuffer.c
+ *
+ * A program for reading input from devices which don't like constant
+ * stopping and starting, such as tape drives.  readbuffer is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * readbuffer is part of chiark backup, a system for backing up GNU/Linux and
+ * other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+ * chiark backup is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ *
+ */
+
+#include "rwbuffer.h"
+
+const char *progname= "readbuffer";
+
+static size_t waitempty;
+
+int main(int argc, const char *const *argv) {
+  int r,reading;
+
+  startup(argv);
+  waitempty= (buffersize*1)/4;
+  reading=1;
+  maxselfd=2;
+  
+  while (!seeneof || used) {
+    
+    FD_ZERO(&readfds);
+    if (reading) {
+      if (used<buffersize-1) {
+       FD_SET(0,&readfds);
+      } else {
+       reading=0;
+      }
+    }
+    FD_ZERO(&writefds); if (used) FD_SET(1,&writefds);
+
+    callselect();
+
+    if (FD_ISSET(0,&readfds)) {
+      r= read(0,rp,min(buffersize-1-used,buf+buffersize-rp));
+      if (!r) {
+        seeneof=1; reading=0;
+      } else if (r<0) {
+        if (!(errno == EAGAIN || errno == EINTR)) { perror("read"); exit(1); }
+      } else {
+        used+= r;
+        rp+= r;
+        if (rp == buf+buffersize) rp=buf;
+      }
+    }
+
+    if (FD_ISSET(1,&writefds)) {
+      assert(used);
+      r= write(1,wp,min(used,buf+buffersize-wp));
+      if (r<=0) {
+        if (!(errno == EAGAIN || errno == EINTR)) { perror("write"); exit(1); }
+      } else {
+        used-= r;
+        wp+= r;
+        if (wp == buf+buffersize) wp=buf;
+      }
+      if (used < waitempty && !seeneof) {
+       reading=1;
+      }
+    }
+  }
+  exit(0);
+}
diff --git a/cprogs/really.8 b/cprogs/really.8
new file mode 100644 (file)
index 0000000..45bca20
--- /dev/null
@@ -0,0 +1,161 @@
+.TH really 8 2001-10-21 chiark-backup
+.SH NAME
+really \- gain privilege or run commands a different user
+.SH SYNOPSIS
+.B really
+.RI [ options ]
+.RI [ "command args... " ]
+.SH DESCRIPTION
+.B really
+checks whether the caller is allowed, and if it is it changes its uids
+and gids according to the command line options and executes the
+specified command.
+.PP
+If no options are specified, the uid will be set to 0 and the gids
+will be left unchanged.
+.PP
+If no command is specified,
+.B really
+will run
+.BR "$SHELL -i" .
+.PP
+A caller is allowed if it has write access to
+.BR /etc/inittab .
+This is most easily achieved by creating or using a suitable group,
+containing all the appropriate users, and making
+.B /etc/inittab
+group-owned by that group and group-writeable.
+.SH OPTIONS
+.TP
+\fB-u\fR \fIusername\fR | \fB--user\fR \fIusername\fR
+Sets the uid, gid, and supplementary group list, according to
+.IR username 's
+entry in the password and group databases.
+.TP
+\fB-i\fR \fIusername\fR | \fB--useronly\fR \fIusername\fR
+Sets only the uid according to
+.IR username 's
+entry in the password database.
+.TP
+\fB-I\fR \fIuid\fR | \fB--uidonly\fR \fIuid\fR
+Sets the uid to the numeric value
+.I uid
+(which need not correspond to any existing user in the password
+database).
+.TP
+\fB-g\fR \fIgroupname\fR | \fB--group\fR \fIgroupname\fR
+.I groupname
+is looked up in the group database and its gid is appended to the
+process's supplementary groups list.  If this is the first gid
+specified it will also be set as the primary gid.
+.TP
+\fB-G\fR \fIgid\fR | \fB--gid\fR \fIgid\fR
+.I gid
+is appended to the process's supplementary groups list.
+.RI ( gid
+need not correspond to any existing group in the group database.)  If
+this is the first gid specified it will also be set as the primary
+gid.
+.TP
+\fB-z\fR | \fB--groupsclear\fR
+Clears the process's supplementary groups list.  When using this
+option you must also specify
+.B -g
+or
+.BR -G .
+The process's groups will then be exactly those specified.  The
+relative position of
+.B -z
+in the argument list is not relevant.
+.TP
+.B \-\-
+Indicates the end of the options.  The next argument (if present) will
+be interpreted as the command name, even if it starts with a hyphen.
+.SH SECURITY CONSIDERATIONS
+.B really
+is designed so that installing it setuid root is extremely unlikely to
+compromise the security of any system.  It will check using
+.BR access (2)
+whether the real user is allowed to write to
+.B /etc/inittab
+and if this check fails
+.B really
+will exit without even attempting to parse its command line.
+.PP
+.B really
+is
+.B not
+designed to be resistant to malicious command line arguments.  Do not
+allow untrusted processes to pass options to really, or to specify the
+command to be run.  Whether it is safe to allow relatively untrusted
+processes to pass options to the command which is to be run depends on
+the behaviour of that command and its security status.
+.PP
+Attempting to use
+.B really
+to drop privilege is dangerous unless the calling environment is very
+well understood.  There are many inherited process properties and
+resources which might be used by the callee to escalate its privilege
+to that of the (root-equivalent) caller.  For this function, it is
+usually better to use
+.B userv
+if possible.
+.SH ENVIRONMENT
+.B really
+does not manipulate the environment at all.  The calling program is
+run in exactly the same environment as the caller passes to
+.BR really .
+In particular,
+.B really
+will not add
+.B sbin
+directories to
+.B PATH
+so
+.BR really -enabled
+accounts will usually need to have these directories on their
+configured
+.B PATH
+to start with.
+.PP
+.B SHELL
+is used to find the default shell to use in interactive mode (ie, when
+no command is specified).
+.SH AUTHOR
+This version of
+.B really
+was written by Ian Jackson <ian@chiark.greenend.org.uk>.
+.PP
+It and this manpage are Copyright (C) 1992-5,2003 Ian Jackson
+<ian@chiark.greenend.org.uk>.
+.PP
+.B really
+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 3,
+or (at your option) any later version.
+.PP
+.B really
+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.
+.PP
+You should have received a copy of the GNU General Public
+License along with this file; if not, consult the Free Software
+Foundation's website at www.fsf.org, or the GNU Project website at
+www.gnu.org.
+.SH AVAILABILITY
+.B really
+is currently part of
+.B chiark-utils
+and is available for download from
+ftp.chiark.greenend.org.uk in /users/ian/chiark-utils/,
+in source and pre-compiled binary form, and also from Ian Jackson's
+cvsweb.
+.SH "SEE ALSO"
+.BR userv (1),
+.BR access (2),
+.BR setresuid (2),
+.BR setresgid (2),
+.BR setgroups (2)
diff --git a/cprogs/really.c b/cprogs/really.c
new file mode 100644 (file)
index 0000000..46db574
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * really.c - program for gaining privilege
+ *
+ * Copyright (C) 1992-3 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include "myopt.h"
+
+void usagemessage(void) {
+  if (fputs("usage: really [<really-option> ...] [--]"
+            " [<command> [<argument/option> ...]]\n"
+            "really-options specifying the user:\n"
+            " if no options given, set the uid to 0;\n"
+            " -u|--user <username>     also sets their default group list\n"
+            " -i|--useronly <username> } set the uid\n"
+            " -I|--uidonly <uid>       }  but inherits the group list\n"
+            "really-options specifying the group:\n"
+            " -z|--groupsclear         only groups specified are to be used\n"
+            " -g|--group <groupname>   } add this to\n"
+            " -G|--gid <gid>           }  the group list\n"
+            "other really-options:\n"
+           " -h|--help                display this message\n"
+           " -R|--chroot <dir>        chroot (but *not* chdir)\n",
+            stderr) == EOF) { perror("write usage"); exit(-1); }
+}
+
+static const char *opt_user, *opt_useronly, *opt_chroot;
+static int opt_groupsclear= 0, opt_ngids= 0, opt_uidonly= -1;
+static int opt_gids[512];
+
+static void af_uidonly(const struct cmdinfo *cip, const char *value) {
+  unsigned long ul;
+  char *ep;
+
+  ul= strtoul(value,&ep,10);
+  if (*ep) { fprintf(stderr,"bad uid `%s'\n",value); exit(-1); }
+  opt_uidonly= ul;
+}
+
+static void af_group(const struct cmdinfo *cip, const char *value) {
+  struct group *gr;
+
+  if (opt_ngids >= sizeof(opt_gids)/sizeof(opt_gids[0]))
+    badusage("too many groups specified");
+  gr= getgrnam(value);
+  if (!gr) { fprintf(stderr,"unknown group `%s'\n",value); exit(-1); }
+  opt_gids[opt_ngids++]= gr->gr_gid;
+}
+
+static void af_gid(const struct cmdinfo *cip, const char *value) {
+  char *ep;
+  unsigned long ul;
+
+  if (opt_ngids >= sizeof(opt_gids)/sizeof(opt_gids[0]))
+    badusage("too many gids specified");
+  ul= strtoul(value,&ep,0);
+  if ((*ep) || (uid_t)ul != ul || ul>INT_MAX) badusage("bad gid `%s'",value);
+  opt_gids[opt_ngids++]= ul;
+}
+
+static void af_help(const struct cmdinfo *cip, const char *value) {
+  usagemessage(); exit(0);
+}
+
+static const struct cmdinfo cmdinfos[]= {
+  { "user",         'u',  1,  0, &opt_user,          0,           },
+  { "useronly",     'i',  1,  0, &opt_useronly,      0            },
+  { "uidonly",      'I',  1,  0, 0,                  af_uidonly   },
+  { "groupsclear",  'z',  0,  &opt_groupsclear, 0,   0,        1  },
+  { "group",        'g',  1,  0, 0,                  af_group     },
+  { "gid",          'G',  1,  0, 0,                  af_gid       },
+  { "chroot",       'R',  1,  0, &opt_chroot,        0            },
+  { "help",         'h',  0,  0, 0,                  af_help      },
+  {  0,              0                                            }
+};
+
+#ifdef REALLY_CHECK_FILE
+static int checkroot(void) {
+  int r;
+  r= access(REALLY_CHECK_FILE,W_OK);
+  if (r) return -1;
+  return 0;
+}
+#endif
+#ifdef REALLY_CHECK_GID
+static int checkroot(void) {
+  gid_t groups[512];
+  int r, i;
+
+  r= getgid(); if (r==REALLY_CHECK_GID) return 0;
+  if (r<0) { perror("getgid check"); exit(-1); }
+  r= getgroups(sizeof(groups)/sizeof(groups[0]),groups);
+  if (r<0) { perror("getgroups check"); exit(-1); }
+  for (i=0; i<r; i++)
+    if (groups[i] == REALLY_CHECK_GID) return 0;
+  return -1;
+}
+#endif
+#ifdef REALLY_CHECK_NONE
+static int checkroot(void) {
+  return 0;
+}
+#endif
+
+int main(int argc, const char *const *argv) {
+  struct passwd *pw= 0;
+  gid_t groups[512];
+  int i, j, ngroups, ngroups_in, maingid, orgmaingid, mainuid, orgmainuid, r;
+  const char *cp;
+  
+  orgmainuid= getuid();
+  if (orgmainuid && checkroot()) { perror("sorry"); exit(-1); }
+  myopt(&argv,cmdinfos);
+
+  if (opt_groupsclear && !opt_ngids)
+    badusage("-z|--groupsclear must be accompanied by some groups");
+  if (opt_user && (opt_useronly || opt_uidonly!=-1))
+    badusage("-u|--user may not be used with -i|--useronly or -I|--uidonly");
+  if (opt_user && opt_groupsclear)
+    badusage("-u|--user may not be used with -z|--groupsclear");
+  if (opt_uidonly != -1 && (uid_t)opt_uidonly != opt_uidonly)
+    badusage("-I|--uidonly value %d is out of range for a uid",opt_uidonly);
+
+  if (!opt_user && !opt_useronly && opt_uidonly==-1 && !opt_ngids) {
+    opt_uidonly= 0;
+  }
+  if (opt_user || opt_useronly) {
+    cp= opt_user ? opt_user : opt_useronly;
+    pw= getpwnam(cp);
+    if (!pw) { fprintf(stderr,"unknown user `%s'\n",cp); exit(-1); }
+    opt_uidonly= pw->pw_uid;
+  }
+  if (opt_chroot) {
+    if (chroot(opt_chroot)) { perror("chroot failed"); exit(-1); }
+  }
+  orgmaingid= getgid();
+  if (orgmaingid<0) { perror("getgid failed"); exit(-1); }
+  if (opt_user) {
+    r= initgroups(opt_user,pw->pw_gid);
+    if (r) { perror("initgroups failed"); exit(-1); }
+    maingid= pw->pw_gid;
+  } else {
+    maingid= -1;
+  }
+  if (opt_groupsclear) {
+    ngroups= 0;
+    if (opt_ngids > sizeof(groups)/sizeof(groups[0])) {
+      fputs("too many groups to set\n",stderr);
+      exit(-1);
+    }
+  } else {
+    ngroups= getgroups(0,0);
+    if (ngroups<0) { perror("getgroups(0,0) failed"); exit(-1); }
+    if (ngroups+opt_ngids > sizeof(groups)/sizeof(groups[0])) {
+      fputs("too many groups already set for total to fit\n",stderr);
+      exit(-1);
+    }
+    ngroups= getgroups(ngroups,groups);
+    if (ngroups<0) { perror("getgroups failed"); exit(-1); }
+  }
+  if (opt_ngids) {
+    maingid= opt_gids[0];
+  }
+  if (opt_ngids || opt_groupsclear) {
+    ngroups_in= ngroups; ngroups= 0;
+    for (i=0; i<ngroups_in; i++) {
+      for (j=0; j<ngroups && groups[j] != groups[i]; j++);
+      if (j<ngroups) continue;
+      groups[ngroups++]= groups[i];
+    }
+    for (i=0; i<opt_ngids; i++) {
+      for (j=0; j<ngroups && groups[j] != opt_gids[i]; j++);
+      if (j<ngroups) continue;
+      groups[ngroups++]= opt_gids[i];
+    }
+    r= setgroups(ngroups,groups);
+    if (r) { perror("setgroups failed"); exit(-1); }
+  }
+  if (maingid != -1) {
+    r= setgid(maingid); if (r) { perror("setgid failed"); exit(-1); }
+    r= setgid(maingid); if (r) { perror("2nd setgid failed"); exit(-1); }
+  }
+  if (opt_uidonly != -1) {
+    mainuid= opt_uidonly;
+  } else {
+    mainuid= orgmainuid;
+  }
+  r= setuid(mainuid); if (r) { perror("setuid failed"); exit(-1); }
+  r= setuid(mainuid); if (r) { perror("2nd setuid failed"); exit(-1); }
+  if (mainuid != 0) {
+    r= seteuid(0); if (r>=0) { fputs("could seteuid 0",stderr); exit(-1); }
+    if (errno != EPERM) {
+      perror("unexpected failure mode for seteuid 0"); exit(-1);
+    }
+  }
+  r= getuid(); if (r<0) { perror("getuid failed"); exit(-1); }
+  if (r != mainuid) { fputs("getuid mismatch",stderr); exit(-1); }
+  r= geteuid(); if (r<0) { perror("geteuid failed"); exit(-1); }
+  if (r != mainuid) { fputs("geteuid mismatch",stderr); exit(-1); }
+  if (maingid != -1) {
+    for (i=0; i<ngroups && maingid != groups[i]; i++);
+    if (i>=ngroups && maingid != orgmaingid) {
+      r= setgid(orgmaingid);
+      if (r>=0) { fputs("could setgid back",stderr); exit(-1); }
+      if (errno != EPERM) {
+        perror("unexpected failure mode for setgid back"); exit(-1);
+      }
+    }
+    r= getgid(); if (r<0) { perror("getgid failed"); exit(-1); }
+    if (r != maingid) { fputs("getgid mismatch",stderr); exit(-1); }
+    r= getegid(); if (r<0) { perror("getegid failed"); exit(-1); }
+    if (r != maingid) { fputs("getegid mismatch",stderr); exit(-1); }
+  }
+  if (!*argv) {
+    cp= getenv("SHELL");
+    if (!cp) cp= "sh";
+    execlp(cp,cp,"-i",(const char*)0);
+  } else {
+    execvp(argv[0],(char**)argv);
+  }
+  perror("exec failed");
+  exit(-1);
+}
diff --git a/cprogs/really.testcases b/cprogs/really.testcases
new file mode 100755 (executable)
index 0000000..153e47c
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/perl
+
+$testuser=   'testac';
+$testgroup=  'testac';
+$testuid=    1000;
+$testgid=    1000;
+@testxgids=  qw(1000);
+$numgid=     50008;
+$othergroup= 'daemon';
+$othergid=   1;
+
+sub parseid ($) {
+    my ($id) = @_;
+    my $orgid= $id;
+    my $out= '';
+    my $part;
+    chomp($id);
+    $id =~ s/^uid=// or return $id =~ m/\n/ ? "ERROR: $`" : "ERROR: $id";
+    $id =~ s/^(\d+)// or return $orgid;
+    $out= $1;
+    $id =~ s/^\([^\)]+\)//; $id =~ s/^\s+// or return $orgid;
+    $id =~ s/^gid=(\d+)// or return $orgid;
+    $out.= " $1";
+    $id =~ s/^\([^\)]+\)//; $id =~ s/^\s+// or return $orgid;
+    $id =~ s/^groups=// or return $orgid;
+    for $part (split(/,/,$id)) {
+        $part =~ s/^(\d+)// or return $orgid;
+        $out.= " $1";
+        $part =~ s/^\([^\)]+\)//; $part eq '' or return $orgid;
+    }
+    return $out;
+}
+
+$org= `id`;
+$org =~ m/^uid=\d+\(([^\)]+)\) gid=\d+\(([^\)]+)\) / or die "$org ?";
+$orguser= $1; $orggroup= $2;
+$org= parseid($org);
+$org =~ m/^\d+ \d+ / or die $org;
+($orguid,$orggid,@orgxgids)= split(/ /,$org);
+
+$tests= <<END;
+-u $testuser
+$testuid $testgid @testxgids
+
+-u $testuser -z
+ERROR: -z|--groupsclear must be accompanied by some groups
+
+-u $testuser -g $othergroup
+$testuid $othergid @testxgids $othergid
+
+-u $testuser -z -g $othergroup
+ERROR: -u|--user may not be used with -z|--groupsclear
+
+-u $testuser -G $numgid -g $othergroup
+$testuid $numgid @testxgids $numgid $othergid
+
+-u $testuser -z -G $numgid -g $othergroup
+ERROR: -u|--user may not be used with -z|--groupsclear
+
+-u $testuser -g $testgroup -G $testgid
+$testuid $testgid @testxgids
+
+-u $testuser -z -g $testgroup -G $testgid
+ERROR: -u|--user may not be used with -z|--groupsclear
+
+-u $testuser -g $othergroup -g $testgroup -G $testgid
+$testuid $othergid @testxgids $othergid
+
+-u $testuser -z -g $othergroup -g $testgroup -G $testgid
+ERROR: -u|--user may not be used with -z|--groupsclear
+
+-u $testuser -G $numgid -g $othergroup -g $testgroup -G $testgid
+$testuid $numgid @testxgids $numgid $othergid
+
+-u $testuser -z -G $numgid -g $othergroup -g $testgroup -G $testgid
+ERROR: -u|--user may not be used with -z|--groupsclear
+
+
+0 $orggid @orgxgids
+
+-i $testuser
+$testuid $orggid @orgxgids
+
+-i $testuser -z
+ERROR: -z|--groupsclear must be accompanied by some groups
+
+-i $testuser -g $othergroup
+$testuid $othergid @orgxgids $othergid
+
+-i $testuser -z -g $othergroup
+$testuid $othergid $othergid
+
+-i $testuser -G $numgid -g $othergroup
+$testuid $numgid @orgxgids $numgid $othergid
+
+-i $testuser -z -G $numgid -g $othergroup
+$testuid $numgid $numgid $othergid
+
+-i $testuser -g $orggroup -G $orggid
+$testuid $orggid @orgxgids
+
+-i $testuser -z -g $orggroup -G $orggid
+$testuid $orggid $orggid
+
+-i $testuser -g $othergroup -g $orggroup -G $orggid
+$testuid $othergid @orgxgids $othergid
+
+-i $testuser -z -g $othergroup -g $orggroup -G $orggid
+$testuid $othergid $othergid $orggid
+
+-i $testuser -G $numgid -g $othergroup -g $orggroup -G $orggid
+$testuid $numgid @orgxgids $numgid $othergid
+
+-i $testuser -z -G $numgid -g $othergroup -g $orggroup -G $orggid
+$testuid $numgid $numgid $othergid $orggid
+
+
+0 $orggid @orgxgids
+
+-z
+ERROR: -z|--groupsclear must be accompanied by some groups
+
+-g $othergroup
+$orguid $othergid @orgxgids $othergid
+
+-z -g $othergroup
+$orguid $othergid $othergid
+
+-G $numgid -g $othergroup
+$orguid $numgid @orgxgids $numgid $othergid
+
+-z -G $numgid -g $othergroup
+$orguid $numgid $numgid $othergid
+
+-g $orggroup -G $orggid
+$orguid $orggid @orgxgids
+
+-z -g $orggroup -G $orggid
+$orguid $orggid $orggid
+
+-g $othergroup -g $orggroup -G $orggid
+$orguid $othergid @orgxgids $othergid
+
+-z -g $othergroup -g $orggroup -G $orggid
+$orguid $othergid $othergid $orggid
+
+-G $numgid -g $othergroup -g $orggroup -G $orggid
+$orguid $numgid @orgxgids $numgid $othergid
+
+-z -G $numgid -g $othergroup -g $orggroup -G $orggid
+$orguid $numgid $numgid $othergid $orggid
+
+
+ERROR: sorry: Permission denied
+./really-test -u $testuser -g staff
+END
+
+@tests= split(/\n/,$tests);
+for ($i=0; $i<$#tests; $i+=3) {
+    $out= `$tests[$i+2] ./really-test $tests[$i] id 2>&1`;
+    $newout= parseid($out);
+    print("OK $tests[$i] ($tests[$i+2])\n"), next if $newout eq $tests[$i+1];
+    die "$newout != $tests[$i+1] ($tests[$i]) $i";
+}
diff --git a/cprogs/rwbuffer.c b/cprogs/rwbuffer.c
new file mode 100644 (file)
index 0000000..4d8503c
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * rwbuffer.c
+ * common definitions for readbuffer/writebuffer
+ *
+ * readbuffer and writebuffer are:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * readbuffer is part of chiark backup, a system for backing up GNU/Linux and
+ * other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+ * chiark backup is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ *
+ */
+
+#include "rwbuffer.h"
+
+#ifndef RWBUFFER_SIZE_MB_DEF
+#define RWBUFFER_SIZE_MB_DEF 16
+#endif
+
+#ifndef RWBUFFER_SIZE_MB_MAX
+#define RWBUFFER_SIZE_MB_MAX 512
+#endif
+
+unsigned char *buf, *wp, *rp;
+int used, seeneof, maxselfd;
+size_t buffersize= RWBUFFER_SIZE_MB_DEF*1024*1024;
+fd_set readfds;
+fd_set writefds;
+
+static int opt_mlock=0;
+
+int min(int a, int b) { return a<=b ? a : b; }
+
+static void usage(FILE *f) {
+  if (fprintf(f,"usage: %s [--mlock] [<megabytes>]\n",progname) < 0)
+    { perror("print usage"); exit(16); }
+}
+
+static void usageerr(const char *what) {
+  fprintf(stderr,"%s: bad usage: %s\n",progname,what);
+  usage(stderr);
+  exit(12);
+}
+
+void nonblock(int fd, int yesno) {
+  int r;
+  r= fcntl(fd,F_GETFL,0); if (r == -1) { perror("fcntl getfl"); exit(8); }
+  if (yesno) r |= O_NDELAY;
+  else r &= ~O_NDELAY;
+  if (fcntl(fd,F_SETFL,r) == -1) { perror("fcntl setfl"); exit(8); }
+}
+
+static void unnonblock(void) {
+  nonblock(0,0); nonblock(1,0);
+}
+
+void startupcore(void) {
+  buf= xmalloc(buffersize);
+
+  if (opt_mlock) {
+    if (mlock(buf,buffersize)) { perror("mlock"); exit(2); }
+  }
+
+  used=0; wp=rp=buf; seeneof=0;
+  if (atexit(unnonblock)) { perror("atexit"); exit(16); }
+}
+
+void startup(const char *const *argv) {
+  const char *arg;
+  char *ep;
+  int shift=-1;
+  
+  assert(argv[0]);
+  
+  while ((arg= *++argv)) {
+    if (!strcmp(arg,"--mlock")) {
+      opt_mlock= 1;
+    } else if (isdigit((unsigned char)arg[0])) {
+      buffersize= strtoul(arg,&ep,0);
+      if (ep[0] && ep[1]) usageerr("buffer size spec. invalid");
+      switch (ep[0]) {
+      case 0: case 'm':  shift= 20;  break;
+      case 'k':          shift= 10;  break;
+      case 'b':          shift= 0;   break;
+      default: usageerr("buffer size unit unknown");
+      }
+      if (buffersize > ((RWBUFFER_SIZE_MB_MAX << 20) >> shift))
+       usageerr("buffer size too big");
+      buffersize <<= shift;
+    } else {
+      usageerr("invalid option");
+    }
+  }
+
+  startupcore();
+  nonblock(0,1); nonblock(1,1);
+}
+
+void *xmalloc(size_t sz) {
+  void *r= malloc(sz); if (!r) { perror("malloc"); exit(6); }; return r;
+}
+
+void callselect(void) {
+  int r;
+  
+  for (;;) {
+    r= select(maxselfd,&readfds,&writefds,0,0);
+    if (r != -1) return;
+    if (errno != EINTR) {
+      perror("select"); exit(4);
+    }
+  }
+}
diff --git a/cprogs/rwbuffer.h b/cprogs/rwbuffer.h
new file mode 100644 (file)
index 0000000..f49b30b
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * rwbuffer.h
+ * common declarations for readbuffer/writebuffer
+ *
+ * readbuffer and writebuffer are:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * readbuffer is part of chiark backup, a system for backing up GNU/Linux and
+ * other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+ * chiark backup is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ *
+ */
+
+#ifndef RWBUFFER_H
+#define RWBUFFER_H
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
+
+#include "dlist.h"
+
+
+int min(int a, int b);
+void callselect(void);
+void startup(const char *const *argv);
+void startupcore(void);
+void *xmalloc(size_t sz);
+void nonblock(int fd, int yesno);
+
+extern const char *progname; /* must be defined by main .c file */
+
+extern unsigned char *buf, *wp, *rp;
+extern int used, seeneof, maxselfd;
+extern size_t buffersize;
+extern fd_set readfds;
+extern fd_set writefds;
+
+
+void wrbufcore_startup(void);
+void wrbufcore_prepselect(int rdfd, int wrfd);
+void wrbufcore_afterselect(int rdfd, int wrfd);
+void fdsetset(int fd, fd_set *set);
+void wrbuf_report(const char *m);
+
+
+#endif /*RWBUFFER_H*/
diff --git a/cprogs/smtpallow.c b/cprogs/smtpallow.c
new file mode 100644 (file)
index 0000000..b4f8765
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * smtpallow.c - ld_preload for hacking with connect() !
+ *
+ * Copyright (C) 1994,1995 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
+
+#include <syscall.h>
+#include <sys/socketcall.h>
+#include <netinet/in.h>
+#include <string.h>
+
+_syscall2(long,socketcall,int,call,unsigned long *,args);
+
+int real_connect(int sockfd, const struct sockaddr *saddr, int addrlen)
+{
+       unsigned long args[3];
+
+       args[0] = sockfd;
+       args[1] = (unsigned long)saddr;
+       args[2] = addrlen;
+       return socketcall(SYS_CONNECT, args);
+}
+
+int connect(int fd, const struct sockaddr *them_any, int addrlen) {
+  struct sockaddr_in *them= (struct sockaddr_in*)them_any;
+  int r,l,i;
+  struct sockaddr_in us;
+  
+  if (addrlen == sizeof(us) &&
+      them->sin_family == AF_INET &&
+      them->sin_port == htons(25)) {
+    memset(&us,0,sizeof(us));
+    us.sin_port= 0;
+    us.sin_family= AF_INET;
+    us.sin_addr.s_addr= INADDR_ANY;
+    r= getsockname(fd,(struct sockaddr*)&us,&l);
+    if (r<0 && errno != EINVAL) return r;
+    if (!ntohs(us.sin_port)) {
+      for (i=1023; i>0; i--) {
+        us.sin_port= htons(i);
+        if (!bind(fd,(struct sockaddr*)&us,sizeof(us))) break;
+        if (errno != EADDRINUSE) return -1;
+      }
+      if (!i) return -1;
+    } else if (r<0) return r;
+  }
+  return real_connect(fd,them_any,addrlen);
+}
diff --git a/cprogs/summer.1 b/cprogs/summer.1
new file mode 100644 (file)
index 0000000..a77b8c9
--- /dev/null
@@ -0,0 +1,157 @@
+.TH SUMMER "1" "December 2006" "Debian" "Chiark-utils-bin"
+.SH NAME
+summer \- print checksum and system metainformation for files
+.SH SYNOPSIS
+.B summer -ACDbfqtx
+.RI [\| startpoint ...]
+.br
+.SH DESCRIPTION
+.B summer
+prints the MD5 checksum of the contents, and the system
+metainformation (ownership, permissions, timestamps, etc.), for a
+file, or recursively for a whole directory tree.
+
+Each command line argument should be a file or directory to be processed;
+if it is a directory then it will be processed and then its contents will
+also be processed, recursively. If no
+.IR startpoint s
+are specified on the command line then
+.B summer
+will read a list of newline-separated startpoints from standard input.
+
+Since
+.B summer
+correctly handles devices, FIFOs and other non-regular files it is useful
+for generating and comparing summaries of arbitrary directory trees where
+md5sum alone would not be.
+.SH OUTPUT FORMAT
+.B summer
+prints one line of information for each filesystem object it processes.
+Each line has the following columns:
+.TS
+tab (@);
+l l.
+@MD5 checksum (in hex) or file type information
+@Size of file in bytes
+@File access rights (in octal)
+@User ID of owner (in decimal)
+@Group ID of owner (in decimal)
+@atime (time of last access, decimal time_t)
+@mtime (time of last modification)
+@ctime (time of last status change)
+@Filename
+.TE
+
+For regular files, the first column is the md5sum. For directories, pipes,
+symlinks and sockets it is the literal string \fBdir\fR, \fBmountpoint\fR, \fBpipe\fR, \fBsymlink\fR or \fBsocket\fR
+as appropriate. For devices it begins with \fBc\fR for character or \fBb\fR for block
+devices, followed by the device number as a single 32 bit hex number and as
+four separate 8 bit decimal numbers (most significant first).
+
+Note that any bytes in the filename other than printing 7-bit ASCII
+are escaped using
+.B \\\x\c
+.I NN
+syntax, where
+.I NN
+are two hex digits; backslashes are also escaped in this way.
+This makes the output unambiguous.  Filenames will be relative
+if the relevant
+.I startpoint
+was relative, and absolute if it was absolute.
+
+For symlinks the filename column is followed by `\fB -> \fR' (note the
+spaces) and the target of the link, again escaped, as above.
+.SH OPTIONS
+.TP
+.B \-A
+Do not print the atime (time of last access). The atime column will be omitted.
+.TP
+.B \-C
+Do not print the ctime (time of last status change). The ctime column will be omitted.
+.TP
+.B \-M
+Do not print the mtime (time of last modification). The mtime column will be omitted.
+.TP
+.B \-D
+Do not print directory sizes. The size column for directories will read \fBdir\fR.
+.TP
+.B \-b
+Do not print mtime (time of last modification) for symbolic links. The mtime field
+for symbolic links will read \fBlink\fR.
+.TP
+.B \-B
+Do not print any times for special files, symlinks,
+sockets, or fifos.  The atime, mtime and ctime fields
+for these objects will read
+.BR char ", " block ", " link  ", " sock " or " pipe
+instead.
+.TP
+.B \-f
+Include information about errors encountered (for example, unreadable files)
+in the output, and continue processing. The default is to print error information
+to standard error and stop immediately an error is encountered.
+.TP
+.B \-x
+Do not cross mountpoints while recursing into subdirectories.  
+Startpoints which are mountpoints \fIare\fR descended into.
+.TP
+.B \-q
+Suppress the progress information which
+.B summer
+normally prints to standard error.
+.TP
+.B \-t
+Set the field separator between the information and the filename to a
+tab character (default is space).
+.TP
+.B \-f
+Normally any errors (problems accessing files including nonexistent
+startpoings, and the like) are fatal; an error message is reported to
+stderr.
+
+With
+.B -f
+errors are nonfatal and the problems are reported inline.  The
+filesystem object with the problem is reported in the normal way
+except that instead of the checksum, the string
+\fB\\[\fR\fIproblem\fR[\fB:\fR\ \fIdetails\fR]\fB]\fR
+appears.  Fields whose value could not be determined are printed
+as \fB?\fR.
+.TP
+.B \-h
+Print a brief usage message to stderr (and do nothing else, exiting nonzero).
+.SH PARSING THE OUTPUT
+If the first character in the line is \fB\\[\fR, then the first
+(checksum or type) field is everything until the first subsequent
+\fB]\fR; this may be of variable length and will be followed by one or
+more spaces.  Otherwise the first field has a fixed width: 64
+characters, the size of an MD5 checksum represented in hex, and is
+followed by a single space.
+
+The metadata fields are space-separated but are also space-padded to a
+minimum width: 10 characters for sizes and times and ids; 4 characters
+for the mode.
+
+The filename field, and optional link target information, are of
+variable length, but they are escaped so that they do not contain
+spaces.
+.SH AUTHOR
+.B summer
+is
+.br
+Copyright (C) 2003-2007 Ian Jackson <ian@chiark.greenend.org.uk>
+
+This manpage was written by Peter Maydell
+and subsequently improved by Ian Jackson.  It is
+.br
+Copyright (C) 2006 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+.br
+Copyright (C) 2007 Ian Jackson <ian@chiark.greenend.org.uk>
+
+This is free software, distributed under the GNU General Public
+Licence, version 3 or (at your option) any later version; see
+/usr/share/doc/chiark-utils-bin/copyright or
+/usr/share/common-licenses/GPL-3
+for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/cprogs/summer.c b/cprogs/summer.c
new file mode 100644 (file)
index 0000000..762b5d6
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * summer - program for summarising (with md5 checksums) filesystem trees
+ *
+ * usage:
+ *    cat startpoints.list | summer >data.list
+ *    summer startpoints... >data.list
+ *  prints md5sum of data-list to stderr
+ */
+/*
+ * Copyright (C) 2003,2006-2007 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
+
+#define _GNU_SOURCE
+
+#include <search.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "nettle/md5-compat.h"
+
+#define MAXFN 2048
+#define MAXDEPTH 1024
+#define CSUMXL 32
+
+static int quiet=0, hidectime=0, hideatime=0, hidemtime=0;
+static int hidedirsize=0, hidelinkmtime=0, hidextime=0, onefilesystem=0;
+static int filenamefieldsep=' ';
+static FILE *errfile;
+
+#define nodeflag_fsvalid       1u
+
+static void malloc_fail(void) { perror("summer: alloc failed"); exit(12); }
+
+static void *mmalloc(size_t sz) {
+  void *r;
+  r= malloc(sz);  if (!r) malloc_fail();
+  return r;
+}
+
+static void *mrealloc(void *p, size_t sz) {
+  void *r;
+  r= realloc(p, sz);  if (!r && sz) malloc_fail();
+  return r;
+}
+
+static void fn_escaped(FILE *f, const char *fn) {
+  int c;
+  while ((c= *fn++)) {
+    if (c>=33 && c<=126 && c!='\\') putc(c,f);
+    else fprintf(f,"\\x%02x",(int)(unsigned char)c);
+  }
+}
+
+static void add_pr(int *pr, int printf_ret) {
+  if (printf_ret == EOF) return;
+  *pr += printf_ret;
+}
+
+static void vproblemx(const char *path, int padto, int per,
+                     const char *fmt, va_list al) {
+  int e=errno, pr=0;
+  
+  if (errfile==stderr) fputs("summer: error: ",stderr);
+  else add_pr(&pr, fprintf(errfile,"\\["));
+  
+  add_pr(&pr, vfprintf(errfile,fmt,al));
+  if (per) add_pr(&pr, fprintf(errfile,": %s",strerror(e)));
+
+  if (errfile==stderr) {
+    fputs(": ",stderr);
+    fn_escaped(stderr,path);
+    fputc('\n',stderr);
+    exit(2);
+  }
+
+  add_pr(&pr, printf("]"));
+
+  while (pr++ < padto)
+    putchar(' ');
+}  
+
+static void problem_e(const char *path, int padto, const char *fmt, ...) {
+  va_list(al);
+  va_start(al,fmt);
+  vproblemx(path,padto,1,fmt,al);
+  va_end(al);
+}
+
+static void problem(const char *path, int padto, const char *fmt, ...) {
+  va_list(al);
+  va_start(al,fmt);
+  vproblemx(path,padto,0,fmt,al);
+  va_end(al);
+}
+
+static void csum_file(const char *path) {
+  FILE *f;
+  MD5_CTX mc;
+  char db[65536];
+  unsigned char digest[16];
+  size_t r;
+  int i;
+
+  f= fopen(path,"rb");
+  if (!f) { problem_e(path,sizeof(digest)*2,"open"); return; }
+  
+  MD5Init(&mc);
+  for (;;) {
+    r= fread(db,1,sizeof(db),f);
+    if (ferror(f)) {
+      problem_e(path,sizeof(digest)*2,"read");
+      fclose(f); return;
+    }
+    if (!r) { assert(feof(f)); break; }
+    MD5Update(&mc,db,r);
+  }
+  MD5Final(digest,&mc);
+  if (fclose(f)) { problem_e(path,sizeof(digest)*2,"close"); return; }
+
+  for (i=0; i<sizeof(digest); i++)
+    printf("%02x", digest[i]);
+}
+
+static void csum_dev(int cb, const struct stat *stab) {
+  printf("%c 0x%08lx %3lu %3lu %3lu %3lu    ", cb,
+        (unsigned long)stab->st_rdev,
+        ((unsigned long)stab->st_rdev & 0x0ff000000U) >> 24,
+        ((unsigned long)stab->st_rdev & 0x000ff0000U) >> 16,
+        ((unsigned long)stab->st_rdev & 0x00000ff00U) >> 8,
+        ((unsigned long)stab->st_rdev & 0x0000000ffU) >> 0);
+}
+
+static void csum_str(const char *s) {
+  printf("%-*s", CSUMXL, s);
+}
+
+static void linktargpath(const char *linktarg) {
+  printf(" -> ");
+  fn_escaped(stdout, linktarg);
+}
+
+static void pu10(void) { printf(" %10s", "?"); }
+
+#define PTIME(stab, memb)  ((stab) ? ptime((stab), (stab)->memb) : pu10())
+
+static void ptime(const struct stat *stab, unsigned long val) {
+  const char *instead;
+
+  if (!hidextime) goto justprint;
+  else if (S_ISCHR(stab->st_mode)) instead= "char";
+  else if (S_ISBLK(stab->st_mode)) instead= "block";
+  else if (S_ISLNK(stab->st_mode)) instead= "link";
+  else if (S_ISSOCK(stab->st_mode)) instead= "sock";
+  else if (S_ISFIFO(stab->st_mode)) instead= "pipe";
+  else {
+  justprint:
+    printf(" %10lu", val);
+    return;
+  }
+
+  printf(" %10s",instead);
+}
+
+struct hardlink {
+  dev_t dev;
+  ino_t ino;
+  char path[1];
+};
+static void *hardlinks;
+
+static int hardlink_compar(const void *av, const void *bv) {
+  const struct hardlink *a=av, *b=bv;
+  if (a->ino != b->ino) return b->ino - a->ino;
+  return b->dev - a->dev;
+}
+
+static void recurse(const char *path, unsigned nodeflags, dev_t fs);
+
+static void node(const char *path, unsigned nodeflags, dev_t fs) {
+  char linktarg[MAXFN+1];
+  struct hardlink *foundhl;
+  const struct stat *stab;
+  struct stat stabuf;
+  int r, mountpoint=0;
+
+  r= lstat(path, &stabuf);
+  stab= r ? 0 : &stabuf;
+
+  foundhl= 0;
+  if (stab && stab->st_nlink>1) {
+    struct hardlink *newhl, **foundhl_node;
+    newhl= mmalloc(sizeof(*newhl) + strlen(path));
+    newhl->dev= stab->st_dev;
+    newhl->ino= stab->st_ino;
+    foundhl_node= tsearch(newhl, &hardlinks, hardlink_compar);
+    if (!foundhl_node) malloc_fail();
+    foundhl= *foundhl_node;
+    if (foundhl!=newhl) {
+      free(newhl); /* hardlink to an earlier object */
+    } else {
+      foundhl= 0; /* new object with link count>1 */
+      strcpy(newhl->path, path);
+    }
+  }
+
+  if (stab) {
+    if ((nodeflags & nodeflag_fsvalid) && stab->st_dev != fs)
+      mountpoint= 1;
+    fs= stab->st_dev;
+    nodeflags |= nodeflag_fsvalid;
+  }
+
+  if (!stab) problem_e(path,CSUMXL,"inaccessible");
+  else if (foundhl) csum_str("hardlink");
+  else if (S_ISREG(stab->st_mode)) csum_file(path);
+  else if (S_ISCHR(stab->st_mode)) csum_dev('c',stab);
+  else if (S_ISBLK(stab->st_mode)) csum_dev('b',stab);
+  else if (S_ISFIFO(stab->st_mode)) csum_str("pipe");
+  else if (S_ISLNK(stab->st_mode)) csum_str("symlink");
+  else if (S_ISSOCK(stab->st_mode)) csum_str("sock");
+  else if (S_ISDIR(stab->st_mode)) csum_str(mountpoint ? "mountpoint" : "dir");
+  else problem(path,CSUMXL,"badobj: 0x%lx", (unsigned long)stab->st_mode);
+
+  if (stab && S_ISLNK(stab->st_mode)) {
+    r= readlink(path, linktarg, sizeof(linktarg)-1);
+    if (r==sizeof(linktarg)) { problem(path,-1,"readlink too big"); r=-1; }
+    else if (r<0) { problem_e(path,-1,"readlink"); }
+    else assert(r<sizeof(linktarg));
+
+    if (r<0) strcpy(linktarg,"\\?");
+    else linktarg[r]= 0;
+  }
+
+  if (stab) {
+    if (S_ISDIR(stab->st_mode) && hidedirsize)
+      printf(" %10s","dir");
+    else
+      printf(" %10lu", 
+            (unsigned long)stab->st_size);
+
+    printf(" %4o %10ld %10ld",
+          (unsigned)stab->st_mode & 07777U,
+          (unsigned long)stab->st_uid,
+          (unsigned long)stab->st_gid);
+  } else {
+    printf(" %10s %4s %10s %10s", "?","?","?","?");
+  }
+
+  if (!hideatime)
+    PTIME(stab, st_atime);
+
+  if (!hidemtime) {
+    if (stab && S_ISLNK(stab->st_mode) && hidelinkmtime)
+      printf(" %10s","link");
+    else
+      PTIME(stab, st_mtime);
+  }
+
+  if (!hidectime)
+    PTIME(stab, st_ctime);
+
+  putchar(filenamefieldsep);
+  fn_escaped(stdout, path);
+
+  if (foundhl) linktargpath(foundhl->path);
+  if (stab && S_ISLNK(stab->st_mode)) linktargpath(linktarg);
+
+  putchar('\n');
+
+  if (ferror(stdout)) { perror("summer: stdout"); exit(12); }
+
+  if (stab && S_ISDIR(stab->st_mode) && !(mountpoint && onefilesystem))
+    recurse(path, nodeflags, fs);
+}
+
+static void process(const char *startpoint) {
+  if (!quiet)
+    fprintf(stderr,"summer: processing: %s\n",startpoint);
+  node(startpoint, 0,0);
+  tdestroy(hardlinks,free);
+  hardlinks= 0;
+}
+
+static int recurse_maxlen;
+
+static int recurse_filter(const struct dirent *de) {
+  int l;
+  if (de->d_name[0]=='.' &&
+      (de->d_name[1]==0 ||
+       (de->d_name[1]=='.' &&
+       de->d_name[2]==0)))
+    return 0;
+  l= strlen(de->d_name);
+  if (l > recurse_maxlen) recurse_maxlen= l;
+  return 1;
+}
+
+static int recurse_compar(const struct dirent **a, const struct dirent **b) {
+  return strcmp((*a)->d_name, (*b)->d_name);
+}
+
+static void recurse(const char *path_or_buf, unsigned nodeflags, dev_t fs) {
+  static char *buf;
+  static int buf_allocd;
+  
+  struct dirent **namelist, *const *de;
+  const char *path_or_0= path_or_buf==buf ? 0 : path_or_buf;
+  int nentries, pathl, esave, buf_want, i;
+
+  pathl= strlen(path_or_buf);
+  recurse_maxlen= 2;
+  nentries= scandir(path_or_buf, &namelist, recurse_filter, recurse_compar);
+  esave= errno;
+  
+  buf_want= pathl+1+recurse_maxlen+1;
+  if (buf_want > buf_allocd) {
+    buf= mrealloc(buf, buf_want);
+    buf_allocd= buf_want;
+  }
+  /* NOTE that path_or_buf is invalid after this point because
+   * it might have been realloc'd ! */
+  if (path_or_0) strcpy(buf,path_or_0);
+
+  buf[pathl]= '/';
+  pathl++;
+  if (nentries < 0) {
+    buf[pathl]= 0;  errno= esave;
+    problem_e(buf,CSUMXL+72,"scandir failed");
+    fn_escaped(stdout,buf);  putchar('\n');
+    return;
+  }
+  for (i=0, de=namelist; i<nentries; i++, de++) {
+    strcpy(buf+pathl, (*de)->d_name);
+    node(buf, nodeflags, fs);
+    free(*de);
+  }
+  free(namelist);
+}
+
+static void from_stdin(void) {
+  char buf[MAXFN+2];
+  char *s;
+  int l;
+
+  if (!quiet)
+    fprintf(stderr, "summer: processing stdin lines as startpoints\n");
+  for (;;) {
+    s= fgets(buf,sizeof(buf),stdin);
+    if (ferror(stdin)) { perror("summer: stdin"); exit(12); }
+    if (!s) { if (feof(stdin)) return; else abort(); }
+    l= strlen(buf);
+    assert(l>0);
+    if (buf[l-1]!='\n') { fprintf(stderr,"summer: line too long\n"); exit(8); }
+    buf[l-1]= 0;
+    process(buf);
+  }
+}
+
+int main(int argc, const char *const *argv) {
+  const char *arg;
+  int c;
+
+  errfile= stderr;
+  
+  while ((arg=argv[1]) && *arg++=='-') {
+    while ((c=*arg++)) {
+      switch (c) {
+      case 'h':
+       fprintf(stderr,
+               "summer: usage: summer startpoint... >data.list\n"
+               "               cat startpoints.list | summer >data.list\n");
+       exit(8);
+      case 'q':
+       quiet= 1;
+       break;
+      case 't':
+       filenamefieldsep= '\t';
+       break;
+      case 'D':
+       hidedirsize= 1;
+       break;
+      case 'b':
+       hidelinkmtime= 1;
+       break;
+      case 'B':
+       hidextime= 1;
+       break;
+      case 'x':
+       onefilesystem= 1;
+       break;
+      case 'C':
+       hidectime= 1;
+       break;
+      case 'A':
+       hideatime= 1;
+       break;
+      case 'M':
+       hidemtime= 1;
+       break;
+      case 'f':
+       errfile= stdout;
+       break;
+      default:
+       fprintf(stderr,"summer: bad usage, try -h\n");
+       exit(8);
+      }
+    }
+    argv++;
+  }
+
+  if (!argv[1]) {
+    from_stdin();
+  } else {
+    if (!quiet)
+      fprintf(stderr, "summer: processing command line args as startpoints\n");
+    while ((arg=*++argv)) {
+      process(arg);
+    }
+  }
+  if (ferror(stdout) || fclose(stdout)) {
+    perror("summer: stdout (at end)"); exit(12);
+  }
+  if (!quiet)
+    fputs("summer: done.\n", stderr);
+  return 0;
+}
diff --git a/cprogs/trivsoundd-start b/cprogs/trivsoundd-start
new file mode 100755 (executable)
index 0000000..9b50700
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e
+pf=`tempfile`
+rm -f $pf
+mknod -m600 $pf p
+exec 3<>$pf
+exec >$pf 4<$pf
+rm -f $pf
+exec 3>&-
+(exec logger -p user.info -t trivsoundd <&4 >/dev/null 2>&1) &
+exec 4<&- 2>&1 </dev/null
+exec ${0%%-start}
diff --git a/cprogs/trivsoundd.8 b/cprogs/trivsoundd.8
new file mode 100644 (file)
index 0000000..796ba93
--- /dev/null
@@ -0,0 +1,25 @@
+.TH writebuffer 1 2001-10-21 chiark-backup
+.SH NAME
+writebuffer \- write output to devices which don't like constant stopping and starting
+.SH SYNOPSIS
+.B writebuffer
+.RB [ --mlock ]
+.RI [ size ]
+.SH DESCRIPTION
+.B writebuffer
+reads data on standard input and writes it to standard output.  It
+will buffer internally up to \fIsize\fR megabytes and will only write
+data when the buffer is at least 75% full or when there is no more
+input to fill the buffer.
+.PP
+It is intended for use in situations where many small writes are
+undesirable for performance reasons, e.g. tape drives.
+.SH OPTIONS
+.TP
+.B --mlock
+Calls
+.BR mlock (2)
+to lock the buffer into memory.
+.SH "SEE ALSO"
+.BR readbuffer (1),
+.BR mlock (2)
diff --git a/cprogs/trivsoundd.c b/cprogs/trivsoundd.c
new file mode 100644 (file)
index 0000000..d1e6636
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * triv-sound-d.c
+ * writebuffer adapted for sound-playing
+ *
+ * readbuffer and writebuffer are:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * readbuffer is part of chiark backup, a system for backing up GNU/Linux and
+ * other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+ * chiark backup is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ *
+ */
+
+#include "rwbuffer.h"
+
+const char *progname= "trivsoundd";
+
+static int maxstartdelay=60, maxbadaccept=10;
+
+struct inqnode {
+  struct inqnode *next, *back;
+  time_t accepted;
+  int fd;
+};
+
+static struct { struct inqnode *head, *tail; } inq;
+static int master, sdev;
+static time_t now;
+
+static void usageerr(const char *m) {
+  fprintf(stderr,"bad usage: %s\n",m);
+  exit(12);
+}
+
+static void bindmaster(const char *bindname) {
+  union {
+    struct sockaddr sa;
+    struct sockaddr_in sin;
+    struct sockaddr_un sun;
+  } su;
+  socklen_t sulen;
+  const char *colon;
+  char *copy, *ep;
+  int r;
+  unsigned long portul;
+  struct hostent *he;
+  struct servent *se;
+
+  memset(&su,0,sizeof(su));
+
+  if (bindname[0]=='/' || bindname[0]=='.') {
+
+    if (strlen(bindname) >= sizeof(su.sun.sun_path))
+      usageerr("AF_UNIX bind path too long");
+    sulen= sizeof(su.sun);
+    su.sun.sun_family= AF_UNIX;
+    strcpy(su.sun.sun_path, bindname);
+
+  } else if (bindname[0] != ':' && (colon= strrchr(bindname,':'))) {
+
+    sulen= sizeof(su.sin);
+    su.sin.sin_family= AF_INET;
+
+    copy= xmalloc(colon - bindname + 1);
+    memcpy(copy,bindname, colon - bindname + 1);
+    copy[colon - bindname]= 0;
+    portul= strtoul(colon+1,&ep,0);
+
+    if (!*ep) {
+      if (!portul || portul>=65536) usageerr("invalid port number");
+      su.sin.sin_port= htons(portul);
+    } else {
+      se= getservbyname(colon+1, "tcp");
+      if (!se) { fprintf(stderr,"unknown service `%s'\n",colon+1); exit(4); }
+      su.sin.sin_port= htons(se->s_port);
+    }
+
+    if (!strcmp(copy,"any")) {
+      su.sin.sin_addr.s_addr= INADDR_ANY;
+    } else if (!inet_aton(copy,&su.sin.sin_addr)) {
+      he= gethostbyname(copy);
+      if (!he) { herror(copy); exit(4); }
+      if (he->h_addrtype != AF_INET ||
+         he->h_length != sizeof(su.sin.sin_addr) ||
+         !he->h_addr_list[0] ||
+         he->h_addr_list[1]) {
+       fprintf(stderr,"hostname lookup `%s' did not yield"
+               " exactly one IPv4 address\n",copy);
+       exit(4);
+      }
+      memcpy(&su.sin.sin_addr, he->h_addr_list[0], sizeof(su.sin.sin_addr));
+    }
+
+  } else {
+    usageerr("unknown bind name");
+    exit(12);
+  }
+
+  master= socket(su.sa.sa_family,SOCK_STREAM,0);
+  if (master<0) { perror("socket"); exit(8); }
+
+  r= bind(master, &su.sa, sulen);
+  if (r) { perror("bind"); exit(8); }
+
+  r= listen(master, 5);
+  if (r) { perror("listen"); exit(8); }
+}
+
+static void opensounddevice(void) {
+  int r;
+  char cbuf[200];
+  
+  sdev= open("/dev/dsp", O_WRONLY);
+  if (sdev<0) { perror("open sound device"); exit(8); }
+
+  snprintf(cbuf, sizeof(cbuf), "sox -t raw -s -w -r 44100 -c 2"
+          " - </dev/null -t ossdsp - >&%d", sdev);
+  r= system(cbuf);  if (r) { fprintf(stderr,"sox gave %d\n",r); exit(5); }
+}
+
+void wrbuf_report(const char *m) {
+  printf("writing %s\n", m);
+}
+
+static void selectcopy(void) {
+  int slave= inq.head ? inq.head->fd : -1;
+  wrbufcore_prepselect(slave, sdev);
+  fdsetset(master,&readfds);
+  callselect();
+  wrbufcore_afterselect(slave, sdev);
+}
+
+static void expireoldconns(void) {
+  struct inqnode *searchold, *nextsearchold;
+      
+  for (searchold= inq.head ? inq.head->next : 0;
+       searchold;
+       searchold= nextsearchold) {
+    nextsearchold= searchold->next;
+    if (searchold->accepted < now-maxstartdelay) {
+      printf("expired %p\n",searchold);
+      LIST_UNLINK(inq,searchold);
+      free(searchold);
+    }
+  }
+}
+
+static void acceptnewconns(void) {
+  static int bad;
+  
+  int slave;
+  struct inqnode *new;
+
+  if (!FD_ISSET(master,&readfds)) return;
+
+  slave= accept(master,0,0);
+  if (slave < 0) {
+    if (!(errno == EINTR ||
+         errno == EAGAIN ||
+         errno == EWOULDBLOCK)) {
+      perror("accept");
+      bad++;
+      if (bad > maxbadaccept) {
+       fprintf(stderr,"accept failures repeating\n");
+       exit(4);
+      }
+    }
+    /* any transient error will just send us round again via select */
+    return;
+  }
+
+  bad= 0;
+  new= xmalloc(sizeof(struct inqnode));
+  new->accepted= now;
+  new->fd= slave;
+  LIST_LINK_TAIL(inq,new);
+
+  printf("accepted %p\n",new);
+}
+
+static void switchinput(void) {
+  struct inqnode *old;
+  if (!seeneof) return;
+  old= inq.head;
+  assert(old);
+  printf("finished %p\n",old);
+  close(old->fd);
+  LIST_UNLINK(inq,old);
+  free(old);
+  seeneof= 0;
+}  
+
+int main(int argc, const char *const *argv) {
+  assert(argv[0]);
+  if (!argv[1] || argv[2] || argv[1][0]=='-')
+    usageerr("no options allowed, must have one argument (bindname)");
+
+  buffersize= 44100*4* 5/*seconds*/;
+
+  opensounddevice();
+  bindmaster(argv[1]);
+  nonblock(sdev,1);
+  nonblock(master,1);
+
+  startupcore();
+  wrbufcore_startup();
+  
+  printf("started\n");
+  for (;;) {
+    selectcopy();
+    if (time(&now)==(time_t)-1) { perror("time(2)"); exit(4); }
+    expireoldconns();
+    acceptnewconns();
+    switchinput();
+  }
+}
diff --git a/cprogs/usernice.c b/cprogs/usernice.c
new file mode 100644 (file)
index 0000000..1f05e19
--- /dev/null
@@ -0,0 +1,67 @@
+/**/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <pwd.h>
+#include <sys/resource.h>
+
+int main(int argc,char **argv) {
+  long l;
+  int mrenice,wrenice,newprio,eflag;
+  uid_t ruid;
+  char *ep;
+  struct passwd *pw;
+
+  mrenice=0;
+  if (argc < 3) {
+    fputs("usernice: too few args\n"
+          " usage: usernice <nicelevel> <command> <arguments>\n"
+          "        usernice <nicelevel>p <pid> <pid> ...\n"
+          "        usernice <nicelevel>u <username|uid> ...\n",
+          stderr);
+    exit(-1);
+  }
+  l= strtol(*++argv,&ep,10);
+  if (*ep == 'p' || *ep == 'u') { mrenice= *ep++; }
+  if (*ep) { fputs("usernice: priority not numeric or bad flags\n",stderr); exit(-1); }
+  if (l<-20 || l>20)
+    { fputs("usernice: priority must be -20 .. 20\n",stderr); exit(-1); }
+  newprio= l;
+  if (mrenice) {
+    eflag=0;
+    while (*++argv) {
+      if (mrenice == 'p') {
+        wrenice= PRIO_PROCESS;
+        l= strtoul(*argv,&ep,10);
+        if (*ep) {
+          fprintf(stderr,"usernice: pid `%s' not numeric\n",*argv); eflag=2;
+          continue;
+        }
+      } else {
+        wrenice= PRIO_USER;
+        l= strtoul(*argv,&ep,10);
+        if (*ep) {
+          pw= getpwnam(*argv);
+          if (!pw) {
+            fprintf(stderr,"usernice: unknown user `%s'\n",*argv); eflag=2;
+            continue;
+          }
+          l= pw->pw_uid;
+        }
+      }
+      if (setpriority(wrenice,l,newprio)) {
+        perror(*argv); if (!eflag) eflag=1;
+      }
+    }
+    exit(eflag);
+  } else {
+    if (setpriority(PRIO_PROCESS,0,newprio))
+      { perror("usernice: setpriority"); exit(-1); }
+    ruid= getuid(); if (ruid == (uid_t)-1) { perror("usernice: getuid"); exit(-1); }
+    if (setreuid(ruid,ruid)) { perror("usernice: setreuid"); exit(-1); }
+    execvp(argv[1],argv+1); perror("usernice: exec"); exit(-1);
+  }
+}
diff --git a/cprogs/usr-local-src-misc-Makefile b/cprogs/usr-local-src-misc-Makefile
new file mode 100644 (file)
index 0000000..c2bfd7a
--- /dev/null
@@ -0,0 +1,22 @@
+CFLAGS=        -Wall -Wwrite-strings -Wmissing-prototypes -Wstrict-prototypes \
+       -Wpointer-arith -O2 -g -DREALLY_CHECK_FILE='"/etc/inittab"'
+LDFLAGS=
+
+TARGETS=really ucgi ucgitarget
+
+all:           $(TARGETS)
+
+ucgi:          ucgi.o ucgicommon.o
+
+ucgitarget:    ucgitarget.o ucgicommon.o
+
+really:                really.o myopt.o
+
+really-test:   really Makefile
+               rm -f really-test
+               cp really really-test
+               really chown root.staff really-test
+               really chmod 4770 really-test
+
+really-check:  really-test really.testcases
+               ./really.testcases
diff --git a/cprogs/watershed.c b/cprogs/watershed.c
new file mode 100644 (file)
index 0000000..3bf9b07
--- /dev/null
@@ -0,0 +1,488 @@
+/*
+ * watershed - an auxiliary verb for optimising away
+ *             unnecessary runs of idempotent commands
+ *
+ * watershed is Copyright 2007 Canonical Ltd
+ * written by Ian Jackson <ian@davenant.greenend.org.uk>
+ * and this version now maintained as part of chiark-utils
+ *
+ *
+ * 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 3 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ *
+ */
+/*
+ *  NB a different fork of this code exists in Ubuntu's udev.
+ */
+/*
+ *
+ * usage: watershed [<options>] <command> [<arg>...]
+ *
+ * options:
+ *   -d|--state-dir <state-dir>
+ *        default is /var/run/watershed for uid 0
+ *                   $HOME/.watershed for others
+ *   -i|--command-id <command-id>
+ *
+ * files used:
+ *    <state-dir>/<command-id>.lock            lockfile
+ *    <state-dir>/<command-id>.cohort          cohort
+ *
+ * default <command-id> is
+ *    hex(sha256(argv[0]+'\0' + argv[1]+'\0' ... argv[argc-1]+'\0')
+ *    '='
+ *    mangled argv[0] (all chars [^-+_0-9A-Za-z] replaced with ?
+ *                     and max 32 chars)
+ *
+ * exit status:
+ *  127      - something went wrong, or process died with some other signal
+ *  SIGPIPE  - process died with SIGPIPE
+ *  x        - process called _exit(x)
+ *
+ * stdin/stdout/stderr:
+ *
+ *  If watershed exits 127 due to some unexpected problem, a message
+ *  is printed to stderr explaining why (obviously).
+ *
+ *  If a watershed invocation ends up running the process, the process
+ *  simply inherits stdin/out/err.  Otherwise stdin/stdout are not used.
+ *
+ *  If the process run for us by another invocation of watershed exits
+ *  zero, or watershed die with the same signal as the process
+ *  (currently just SIGPIPE), nothing is printed to stderr.  Otherwise
+ *  (ie, failure of the actual process, in another invocation),
+ *  watershed prints a description of the wait status to stderr, much
+ *  as the shell might.
+ *
+ */
+/*
+ * gcc -Wall -Wwrite-strings -Wmissing-prototypes watershed.c -o watershed /usr/lib/libnettle.a
+ */
+/*
+ *
+ * Theory:
+ *
+ *  We consider only invocations with a specific command id (and state
+ *  directory), since other invocations are completely independent by
+ *  virtue of having different state file pathnames and thus different
+ *  state files.  Normally, a command id corresponds to invocations
+ *  with a particular set of command line arguments and a state
+ *  directory corresponds to a particular euid; environment variable
+ *  settings and other inherited process properties are disregarded.
+ *
+ *  A `cohort' is a set of invocations which can be coalesced into one
+ *  run of the command.  For each cohort there is a file, the cohort
+ *  file (which may not yet exist, may exist and have a name, or may
+ *  be unliked).
+ *
+ *  An `invocation' is an invocation of the `watershed' program.  A
+ *  `process' is an invocation of the requested command.
+ *
+ *  There is always one current cohort, in one of the following
+ *  two states:
+ *
+ *   * Empty
+ *     No invocations are in this cohort yet.
+ *     The cohort filename is ENOENT.
+ *     This is the initial state for a cohort, and the legal next
+ *     state is Accumulating.
+ *
+ *   * Accumulating
+ *     The process for this run has not yet started, so that new
+ *     invocations arriving would be satisfied if this cohort were to
+ *     run.
+ *     The cohort filename refers to this cohort's file.
+ *     The legal next state for the cohort is Ready.
+ *
+ *  Additionally, there may be older cohorts in the following states:
+ *
+ *   * Ready
+ *     The command for this cohort has not yet been run.
+ *     The cohort file has no name and is empty.
+ *     Only one cohort, the lockholder's, may be in this state.
+ *     The next legal states are Running, or exceptionally Forgotten
+ *     (if the lockholder crashes and is the only invocation in the
+ *     cohort).
+ *
+ *   * Running
+ *     The lockholder is running the command for this cohort.
+ *     This state is identical to Ready from the point of view
+ *     of all invocations except the lockholder.
+ *     The legal next states are Done (the usual case), or (if the
+ *     lockholder crashes) Ready or Forgotten.
+ *
+ *   * Done
+ *     The main process for this run has finished.
+ *     The cohort file has no name and contains sizeof(int)
+ *     bytes, the `status' value from waitpid.
+ *     The legal next state is Forgotten.
+ *
+ *   * Forgotten
+ *     All invocations have finished and the cohort file no longer
+ *     exists.  This is the final state.
+ *
+ *  Only the lockholder may move a cohort between states, except that
+ *  any invocation may make the current Empty cohort become
+ *  Accumulating, and that the kernel will automatically move a cohort
+ *  from Running to Ready or from Done to Forgotten, when appropriate.
+ *
+ * 
+ * Algorithm:
+ *
+ *   1. Open the cohort file (O_CREAT|O_RDWR)   so our cohort is
+ *                                                 Accumulating/Ready/
+ *                                                    Running/Done
+ *                              
+ *   2. Acquire lock (see below)                so lockholder's cohort is
+ *                                                Accumulating/Ready/Done
+ *   3. fstat the open cohort file
+ *         If it is nonempty:                      Done
+ *          Read status from it and exit.
+ *         Otherwise, if nonzero link count:       Accumulating
+ *          Unlink the cohort filename
+ *         Otherwise:                              Ready
+ *
+ *   4. Fork and run the command                   Running
+ *       and wait for it
+ *
+ *   5. Write the wait status to the cohort file   Done
+ *
+ *                      
+ *   6. Release the lock                        so we are no longer lockholder
+ *                                              but our cohort is still
+ *                                                 Done
+ *
+ *   8. Exit                                       Done/Forgotten
+ *
+ *  If an invocation crashes (ie, if watershed itself fails, rather
+ *  than if the command does) then that invocation's caller will be
+ *  informed of the error.
+ *
+ *  If the lockholder crashes with the cohort in:
+ *
+ *     Accumulating:
+ *       The cohort remains in Accumulating and another invocation can
+ *       become the lockholder.  If there are never any other
+ *       invocations then the lockfile and cohort file will not be
+ *       cleaned up (see below).
+ *
+ *     Running/Ready:
+ *       The cohort goes from Running back to Ready (see above) and
+ *       another invocation in the same cohort will become the
+ *       lockholder and run it.  If there is no other invocation in
+ *       the cohort the cohort goes to Forgotten although the lockfile
+ *       will not be cleaned up - see below.
+ *
+ *     Done:
+ *       If there are no more invocations, the cohort is Forgotten but
+ *       the lockfile is not cleaned up.
+ *
+ * Lockfile:
+ *
+ *  There is one lock for all cohorts.  The lockholder is the
+ *  invocation which holds the fcntl lock on the file whose name is
+ *  the lockfile.  The lockholder (and no-one else) may unlink the
+ *  lockfile.
+ *
+ *  To acquire the lock:
+ *
+ *   1. Open the lockfile (O_CREAT|O_RDWR)
+ *   2. Acquire fcntl lock (F_SETLKW)
+ *   3. fstat the open lockfile and stat the lockfile filenmae
+ *      If inode numbers disagree, close lockfile and start
+ *      again from the beginning.
+ *
+ *  To release the lock, unlink the lockfile and then either close it
+ *  or exit.  Crashing will also release the lock but leave the
+ *  lockfile lying around (which is slightly untidy but not
+ *  incorrect); if this is a problem a cleanup task could periodically
+ *  acquire and release the lock for each lockfile found:
+ *
+ * Cleanup:
+ *
+ *  As described above and below, stale cohort files and lockfiles can
+ *  result from invocations which crashed if the same command is never
+ *  run again.  Such cohorts are always in Empty or Accumulating.
+ *
+ *  If it became necessary to clean up stale cohort files and
+ *  lockfiles resulting from crashes, the following algorithm should
+ *  be executed for each lockfile found, as a cleanup task:
+ *
+ *   1. Acquire the lock.
+ *      This makes us the lockholder.           and the current cohort is in
+ *                                                 Empty/Accumulating
+ *
+ *                                              so now that cohort is
+ *   2. Unlink the cohort file, ignoring ENOENT.   Ready/Forgotten
+ *   3. Release the lock.                          Ready/Forgotten
+ *   4. Exit.                                      Ready/Forgotten
+ *
+ *  This consists only of legal transitions, so if current cohort
+ *  wasn't stale, it will have been moved to Ready and some other
+ *  invocation in this cohort will become the lockholder and as normal
+ *  from step 4 of the main algorithm.  If the cohort was stale it
+ *  will go to Forgotten straight away.
+ *
+ *  A suitable cleanup script, on a system with with-lock-ex, is: */
+ //     #!/bin/sh
+ //     set -e
+ //     if [ $# != 1 ]; echo >&2 'usage: cleanup <statedir>'; exit 1; fi
+ //     cd "$1"
+ //     for f in ./*.lock; do
+ //       with-lock-ex -w rm -f "${f%.lock}.cohort"
+ //     done
+/*
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <locale.h>
+#include <libintl.h>
+
+#include <nettle/sha.h>
+
+static const struct option os[]= {
+  { "--state-dir", 1,0,'d' },
+  { "--command-id",1,0,'i' },
+  { 0 }
+};
+
+static const char *state_dir, *command_id, *command;
+static const char *lock_path, *cohort_path;
+
+static int cohort_fd, lock_fd;
+
+
+#define _(x) gettext(x)
+
+#define NOEINTR_TYPED(type,assign) do{                 \
+    while ((assign)==(type)-1 && errno==EINTR) {}      \
+  }while(0)
+
+#define NOEINTR(assign) \
+    NOEINTR_TYPED(int,(assign))
+
+#define CHECKED(value,what) do{                        \
+    NOEINTR(r= (value));                       \
+    if (r<0) diee((what));                     \
+  }while(0)
+
+
+static void badusage(void) {
+  fputs(_("usage: watershed [<options>] <command>...\n"
+         "options: -d|--state-dir <directory>  -i|--command-id <id>\n"
+         "see /usr/share/doc/chiark-utils-bin/watershed.txt\n"),
+         stderr);
+  exit(127);
+}
+static void die(const char *m) {
+  fprintf(stderr,_("watershed: error: %s\n"), m);
+  exit(127);
+}
+static void diee(const char *m) {
+  fprintf(stderr,_("watershed: error: %s failed: %s\n"), m, strerror(errno));
+  exit(127);
+}
+static void dieep(const char *action, const char *path) {
+  fprintf(stderr,_("watershed: error: could not %s `%s': %s\n"),
+         action, path, strerror(errno));
+  exit(127);
+}
+
+static char *m_vasprintf(const char *fmt, va_list al) {
+  char *s;  int r;
+  r= vasprintf(&s,fmt,al);
+  if (r==-1) diee("vasprintf");
+  return s;
+}
+static char *m_asprintf(const char *fmt, ...) {
+  char *s;  va_list al;
+  va_start(al,fmt); s= m_vasprintf(fmt,al); va_end(al);
+  return s;
+}
+
+static void parse_args(int argc, char *const *argv) {
+  int o;
+  for (;;) {
+    o= getopt_long(argc, argv, "+d:i:", os,0);
+    if (o==-1) break;
+    switch (o) {
+    case 'd': state_dir= optarg; break;
+    case 'i': command_id= optarg; break;
+    default: badusage();
+    }
+  }
+  command= argv[optind];
+  if (!command) badusage();
+  if (!state_dir) state_dir= getenv("WATERSHED_STATEDIR");
+  if (!state_dir) {
+    uid_t u= geteuid();  if (u==(uid_t)-1) diee("getuid");
+    if (u) {
+      const char *home= getenv("HOME");
+      if (!home) die(_("HOME not set, no --state-dir option"
+                      " supplied, not root"));
+      state_dir= m_asprintf("%s/.watershed", home);
+    } else {
+      state_dir= "/var/run/watershed";
+    }
+  }
+  if (!command_id) {
+    char *const *ap;
+    struct sha256_ctx sc;
+    unsigned char dbuf[SHA256_DIGEST_SIZE], *p;
+    char *construct, *q;
+    int i, c;
+    
+    sha256_init(&sc);
+    for (ap= argv+optind; *ap; ap++) sha256_update(&sc,strlen(*ap)+1,*ap);
+    sha256_digest(&sc,sizeof(dbuf),dbuf);
+
+    construct= m_asprintf("%*s#%.32s", (int)sizeof(dbuf)*2,"", command);
+    for (i=sizeof(dbuf), p=dbuf, q=construct; i; i--,p++,q+=2)
+      sprintf(q,"%02x",*p);
+    *q++= '=';
+    while ((c=*q++)) {
+      if (!(c=='-' || c=='+' || c=='_' || isalnum((unsigned char)c)))
+       q[-1]= '?';
+    }
+    command_id= construct;
+  }
+
+  lock_path= m_asprintf("%s/%s.lock", state_dir, command_id);
+  cohort_path= m_asprintf("%s/%s.cohort", state_dir, command_id);
+}
+
+static void acquire_lock(void) {
+  struct stat current_stab, our_stab;
+  struct flock fl;
+  int r;
+
+  for (;;) {
+    NOEINTR( lock_fd= open(lock_path, O_CREAT|O_RDWR, 0600) );
+    if (lock_fd<0) diee("open lock");
+
+    memset(&fl,0,sizeof(fl));
+    fl.l_type= F_WRLCK;
+    fl.l_whence= SEEK_SET;
+    CHECKED( fcntl(lock_fd, F_SETLKW, &fl), "acquire lock" );
+    
+    CHECKED( fstat(lock_fd, &our_stab), "fstat our lock");
+
+    NOEINTR( r= stat(lock_path, &current_stab) );
+    if (!r &&
+       our_stab.st_ino == current_stab.st_ino &&
+       our_stab.st_dev == current_stab.st_dev) break;
+    if (r && errno!=ENOENT) diee("fstat current lock");
+    
+    close(lock_fd);
+  }
+}
+static void release_lock(void) {
+  int r;
+  CHECKED( unlink(lock_path), "unlink lock");
+}
+
+static void report(int status) {
+  int v;
+  if (WIFEXITED(status)) {
+    v= WEXITSTATUS(status);
+    if (v) fprintf(stderr,_("watershed: `%s' failed with error exit status %d"
+                           " (in another invocation)\n"), command, v);
+    exit(status);
+  }
+  if (WIFSIGNALED(status)) {
+    v= WTERMSIG(status); assert(v);
+    if (v == SIGPIPE) raise(v);
+    fprintf(stderr,
+           WCOREDUMP(status)
+           ? _("watershed: `%s' died due to fatal signal %s (core dumped)\n")
+           : _("watershed: `%s' died due to fatal signal %s\n"),
+           command, strsignal(v));
+  } else {
+    fprintf(stderr, _("watershed: `%s' failed with"
+                     " crazy wait status 0x%x\n"), command, status);
+  }
+  exit(127);
+}
+
+int main(int argc, char *const *argv) {
+  int status, r, dir_created=0, l;
+  unsigned char *p;
+  struct stat cohort_stab;
+  pid_t c, c2;
+  
+  setlocale(LC_MESSAGES,""); /* not LC_ALL, see use of isalnum below */
+  parse_args(argc,argv);
+
+  for (;;) {
+    NOEINTR( cohort_fd= open(cohort_path, O_CREAT|O_RDWR, 0644) );
+    if (cohort_fd>=0) break;
+    if (errno!=ENOENT) dieep(_("open/create cohort state file"), cohort_path);
+    if (dir_created++) die("open cohort state file still ENOENT after mkdir");
+    NOEINTR( r= mkdir(state_dir,0700) );
+    if (r && errno!=EEXIST) dieep(_("create state directory"), state_dir);
+  }
+
+  acquire_lock();
+
+  CHECKED( fstat(cohort_fd, &cohort_stab), "fstat our cohort");
+  if (cohort_stab.st_size) {
+    if (cohort_stab.st_size < sizeof(status))
+      die(_("cohort status file too short (disk full?)"));
+    else if (cohort_stab.st_size != sizeof(status))
+      die("cohort status file too long");
+    NOEINTR( r= read(cohort_fd,&status,sizeof(status)) );
+    if (r==-1) diee("read cohort");
+    if (r!=sizeof(status)) die("cohort file read wrong length");
+    release_lock(); report(status);
+  }
+
+  if (cohort_stab.st_nlink)
+    CHECKED( unlink(cohort_path), "unlink our cohort");
+
+  NOEINTR_TYPED(pid_t, c= fork() );  if (c==(pid_t)-1) diee("fork");
+  if (!c) {
+    close(cohort_fd); close(lock_fd);
+    execvp(command, argv+optind);
+    fprintf(stderr,_("watershed: failed to execute `%s': %s\n"),
+           command, strerror(errno));
+    exit(127);
+  }
+
+  NOEINTR( c2= waitpid(c, &status, 0) );
+  if (c2==(pid_t)-1) diee("waitpid");
+  if (c2!=c) die("waitpid gave wrong pid");
+
+  for (l=sizeof(status), p=(void*)&status; l>0; l-=r, p+=r)
+    CHECKED( write(cohort_fd,p,l), _("write result status"));
+
+  release_lock();
+  if (!WIFEXITED(status)) report(status);
+  exit(WEXITSTATUS(status));
+}
diff --git a/cprogs/with-lock-ex.1 b/cprogs/with-lock-ex.1
new file mode 100644 (file)
index 0000000..941d1d9
--- /dev/null
@@ -0,0 +1,147 @@
+.TH WITH-LOCK-EX "1" "July 2003" "Debian" "Chiark-utils-bin"
+.SH NAME
+with-lock-ex \- file locker
+.SH SYNOPSIS
+.B with-lock-ex
+.BR \-w \||\| \-q \||\| \-f
+.I lockfile command 
+.IR args \ \|.\|.\|.
+.br
+.SH DESCRIPTION
+with-lock-ex will open and lock the lockfile for writing and then feed
+the remainder of its arguments to
+.BR exec (2);
+when that process terminates the fd will be closed and the file
+unlocked automatically by the kernel.
+.PP
+If the file does not exist it is created, with permissions
+.B rw
+for each user class for which the umask has
+.BR w .
+.SH OPTIONS
+.TP
+.B \-w
+Wait for the lock to be available.
+.TP
+.B \-f
+Fail (printing a message to stderr and exiting 255) if the lock cannot
+be acquired immediately because another process has it.
+.TP
+.B \-q
+Silently do nothing (ie, exit 0 instead of executing the specified
+process) if the lock cannot be acquired immediately because another
+process has it.
+.SH STALE LOCKS
+The locking protocol used does not suffer from stale locks.  If the
+lock cannot be acquired, one or more running processes must currently
+hold the lock; if the lock needs to be freed those processes should be
+killed.
+.PP
+Under no circumstances should `stale lock cleaner' cron jobs, or the
+like, be instituted.  In systems where a great many locks may exist,
+old lockfiles may be removed from cron but only if each lock is
+acquired before the lockfile is removed, for example with
+.IP
+.B with-lock-ex -q
+.I lockfile
+.B rm
+.I lockfile
+.SH DEADLOCKS
+There is no deadlock detection.  In a system with several locks, a
+lock hierarchy should be established, such that for every pair of
+locks
+.I A
+and
+.I B
+which a process might lock simultaneously, either
+.IR A > B
+or
+.IR B > A
+where the relation > is transitive and noncyclic.
+.PP
+Then, for any two locks
+.I X
+and
+.I Y
+with
+.IR X > Y
+it is forbidden to acquire
+.I X
+while holding
+.IR Y .
+Instead, acquire
+.I X
+first, or release
+.I Y
+before (re)acquiring
+.I X
+and
+.I Y
+in that order.
+.PP
+(There are more complicated ways of avoiding deadlocks, but a lock
+hierarchy is simple to understand and implement.  If it does not meet
+your needs, consult the literature.)
+.SH LOCKING PROTOCOL
+The locking protocol used by
+.B with-lock-ex
+is as follows:
+.PP
+The lock is held by a process (or group of processes) which holds an
+fcntl exclusive lock on the first byte of the plain file which has the
+specified name.  A holder of the lock (and only a holder of the lock)
+may delete the file or change the inode to which the name refers, and
+as soon as it does so it ceases to hold the lock.
+.PP
+Any process may create the file if it does not exist.  There is no
+need for the file to contain any actual data.  Indeed, actually using
+the file for data storage is strongly disrecommended, as this will
+foreclose most strategies for reliable update.  Use a separate
+lockfile instead.
+.PP
+Ability to obtain the lock corresponds to write permission on the file
+(and of course permission to create the  file, if it does not already
+exist).  However, processes with only read permission on the file can
+prevent the lock being acquired at all; therefore lockfiles should not
+usually be world-readable.
+.PP
+When a (group of) processes wishes to acquire the lock, it should open
+the file
+(with
+.BR O_CREAT )
+and lock it with
+.BR fcntl (2)
+.BR F_RWLCK ,
+operation
+.B F_SETLK
+or
+.BR F_SETLKW .
+If this succeeds it should fstat the file descriptor it has, and the
+file by its path.  If the device and inode match then the lock has
+been acquired and remains acquired until that group of processes
+changes which file the name refers to, deletes the file, or releases
+the fcntl lock.  If they do not then another process acquired the lock
+and deleted the file in the meantime; you must now close your
+filedescriptor and start again.
+.B with-lock-ex
+follows this specification.
+.PP
+Note that
+.BR flock (2)
+is a different kind of lock to
+.BR fcntl (2).
+.B with-lock-ex
+uses
+.BR fcntl .
+.SH AUTHOR
+This Manual page was written by Matthew Vernon <matthew@debian.org>
+and enhanced by Ian Jackson <ian@chiark.greenend.org.uk>, in 2003, but
+may be used by anyone.
+.SH COPYRIGHT
+with-lock-ex was written by Ian Jackson <ian@chiark.greenend.org.uk>
+in 1993, 1994, 1995, 1996, 1998, 1999. He has placed it in the public
+domain. 
+.SH "SEE ALSO"
+.BR fcntl (2),
+.BR flock (2),
+.BR chmod (2)
diff --git a/cprogs/with-lock-ex.c b/cprogs/with-lock-ex.c
new file mode 100644 (file)
index 0000000..1850d1f
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * File locker
+ *
+ * Usage: with-lock-ex -<mode> <lockfile> <command> <args>...
+ *
+ * modes are
+ *  w    wait for the lock
+ *  f    fail if the lock cannot be acquired
+ *  q    silently do nothing if the lock cannot be acquired
+ *
+ * with-lock-ex will open and lock the lockfile for writing and
+ * then feed the remainder of its arguments to exec(2); when
+ * that process terminates the fd will be closed and the file
+ * unlocked automatically by the kernel.
+ *
+ * If invoked as with-lock, behaves like with-lock-ex -f (for backward
+ * compatibility with an earlier version).
+ *
+ * This file written by me, Ian Jackson, in 1993, 1994, 1995, 1996,
+ * 1998, 1999.  I hereby place it in the public domain.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+
+static const char *cmd;
+
+static void fail(const char *why) __attribute__((noreturn));
+
+static void fail(const char *why) {
+  fprintf(stderr,"with-lock-ex %s: %s: %s\n",cmd,why,strerror(errno));
+  exit(255);
+}
+
+int main(int argc, char **argv) {
+  int fd, mode, um;
+  struct stat stab, fstab;
+  long cloexec;
+  struct flock fl;
+  const char *p;
+
+  if (argc >= 3 && !strcmp((p= strrchr(argv[0],'/')) ? ++p : argv[0], "with-lock")) {
+    mode= 'f';
+  } else if (argc < 4 || argv[1][0] != '-' || argv[1][2] ||
+            ((mode= argv[1][1]) != 'w' && mode != 'q' && mode != 'f')) {
+    fputs("usage: with-lock-ex -w|-q|-f <lockfile> <command> <args>...\n"
+         "       with-lock             <lockfile> <command> <args>...\n",
+         stderr);
+    exit(255);
+  } else {
+    argv++; argc--;
+  }
+  cmd= argv[2];
+  um= umask(0777); if (um==-1) fail("find umask");
+  if (umask(um)==-1) fail("reset umask");
+
+  for (;;) {
+  
+    fd= open(argv[1],O_RDWR|O_CREAT,0666&~(um|((um&0222)<<1)));
+    if (fd<0) fail(argv[1]);
+  
+    for (;;) {
+      fl.l_type= F_WRLCK;
+      fl.l_whence= SEEK_SET;
+      fl.l_start= 0;
+      fl.l_len= 1;
+      if (fcntl(fd, mode=='w' ? F_SETLKW : F_SETLK, &fl) != -1) break;
+      if (mode=='q' &&
+         (errno == EAGAIN || errno == EWOULDBLOCK || errno == EBUSY))
+       exit(0);
+      if (errno != EINTR) fail("could not acquire lock");
+    }
+
+    if (fstat(fd, &fstab)) fail("could not fstat lock fd");
+    if (stat(argv[1], &stab)) {
+      if (errno != ENOENT) fail("could not stat lockfile");
+    } else {
+      if (stab.st_dev == fstab.st_dev &&
+         stab.st_ino == fstab.st_ino) break;
+    }
+    close(fd);
+  }
+
+  cloexec= fcntl(fd, F_GETFD); if (cloexec==-1) fail("fcntl F_GETFD");
+  cloexec &= ~1;
+  if (fcntl(fd, F_SETFD, cloexec)==-1) fail("fcntl F_SETFD");
+
+  execvp(cmd,argv+2);
+  fail("unable to execute command");
+}
diff --git a/cprogs/wrbufcore.c b/cprogs/wrbufcore.c
new file mode 100644 (file)
index 0000000..65fc2be
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * wrbufcore.c
+ *
+ * Core of algorithm for writing output to devices which don't like
+ * constant stopping and starting, such as tape drives.  This is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * writebuffer is part of chiark backup, a system for backing up GNU/Linux
+ * and other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+ * chiark backup is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ *
+ */
+
+#include "rwbuffer.h"
+
+static size_t waitfill;
+
+int writing;
+
+void wrbufcore_startup(void) {
+  waitfill= (buffersize*3)/4;
+  writing=0;
+  maxselfd=0;
+}
+
+void fdsetset(int fd, fd_set *set) {
+  FD_SET(fd,set);
+  if (fd >= maxselfd) maxselfd= fd+1;
+}
+
+void wrbufcore_prepselect(int rdfd, int wrfd) {
+  FD_ZERO(&readfds);
+  if (rdfd>=0 && !seeneof && used+1<buffersize) fdsetset(rdfd,&readfds);
+  
+  FD_ZERO(&writefds);
+  if (writing) fdsetset(wrfd,&writefds);
+}
+
+void wrbufcore_afterselect(int rdfd, int wrfd) {
+  int r;
+    
+  if (FD_ISSET(wrfd,&writefds) &&
+      !(rdfd>=0 && FD_ISSET(rdfd,&readfds)) &&
+      !used) {
+    wrbuf_report("stopping");
+    writing= 0;
+    FD_CLR(wrfd,&writefds);
+  }
+
+  if (rdfd>=0 && FD_ISSET(rdfd,&readfds)) {
+    r= read(rdfd,rp,min(buffersize-1-used,buf+buffersize-rp));
+    if (!r) {
+      seeneof=1; writing=1;
+      wrbuf_report("seeneof");
+    } else if (r<0) {
+      if (!(errno == EAGAIN || errno == EINTR)) { perror("read"); exit(1); }
+    } else {
+      used+= r;
+      rp+= r;
+      if (rp == buf+buffersize) rp=buf;
+    }
+    if (used > waitfill) {
+      if (!writing) wrbuf_report("starting");
+      writing=1;
+    }
+  }
+
+  if (FD_ISSET(wrfd,&writefds) && used) {
+    r= write(wrfd,wp,min(used,buf+buffersize-wp));
+    if (r<=0) {
+      if (!(errno == EAGAIN || errno == EINTR)) { perror("write"); exit(1); }
+    } else {
+      used-= r;
+      wp+= r;
+      if (wp == buf+buffersize) wp=buf;
+    }
+  }
+}
diff --git a/cprogs/writebuffer.1 b/cprogs/writebuffer.1
new file mode 100644 (file)
index 0000000..b3f28ab
--- /dev/null
@@ -0,0 +1,29 @@
+.TH writebuffer 1 2001-10-21 chiark-backup
+.SH NAME
+writebuffer \- write output to devices which don't like constant stopping and starting
+.SH SYNOPSIS
+.B writebuffer
+.RB [ --mlock ]
+.RI [ size ]
+.SH DESCRIPTION
+.B writebuffer
+reads data on standard input and writes it to standard output.  It
+will buffer internally up to \fIsize\fR megabytes and will only write
+data when the buffer is at least 75% full or when there is no more
+input to fill the buffer.
+.PP
+\fIsize\fR may also be suffixed with
+.BR m ", " k ", or " b
+to indicate that it is in megabytes (2^20), kilobytes (2^10) or bytes.
+.PP
+It is intended for use in situations where many small writes are
+undesirable for performance reasons, e.g. tape drives.
+.SH OPTIONS
+.TP
+.B --mlock
+Calls
+.BR mlock (2)
+to lock the buffer into memory.
+.SH "SEE ALSO"
+.BR readbuffer (1),
+.BR mlock (2)
diff --git a/cprogs/writebuffer.c b/cprogs/writebuffer.c
new file mode 100644 (file)
index 0000000..da47cea
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * writebuffer.c
+ *
+ * A program for writing output to devices which don't like constant
+ * stopping and starting, such as tape drives.  writebuffer is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * writebuffer is part of chiark backup, a system for backing up GNU/Linux
+ * and other UN*X-compatible machines, as used on chiark.greenend.org.uk.
+ * chiark backup is:
+ *  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ *  Copyright (C) 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ *
+ */
+
+#include "rwbuffer.h"
+
+const char *progname= "writebuffer";
+
+void wrbuf_report(const char *m) { }
+
+int main(int argc, const char *const *argv) {
+  startup(argv);
+  wrbufcore_startup();
+  while (!seeneof || used) {
+    wrbufcore_prepselect(0,1);
+    callselect();
+    wrbufcore_afterselect(0,1);
+  }
+  exit(0);
+}
diff --git a/cprogs/xbatmon-simple.c b/cprogs/xbatmon-simple.c
new file mode 100644 (file)
index 0000000..6ca3294
--- /dev/null
@@ -0,0 +1,856 @@
+/*
+ * display outputs, per line:
+ *
+ *   Remaining:         | Empty:       | Degraded:
+ *     blue     |  black       |  dimgrey      discharging
+ *     green    |  black       |  dimgrey      charging
+ *     cyan     |  black       |  dimgrey      charged
+ *     grey     |  black       |  dimgrey      charging&discharching!
+ *     lightgrey |  black      |  dimgrey      none of the above
+ *     blue     |  red         |  dimgrey      discharging - low!
+ *     green    |  red         |  dimgrey      charging - low
+ *     cyan     |  red         |  dimgrey      charged - low [1]
+ *     grey     |  red         |  dimgrey      charging&discharching, low [1]
+ *       ...  darkgreen  ...                   no batteries present
+ *       ...  yellow  ...                      error
+ *
+ * [1] battery must be quite badly degraded
+ */
+/*
+ * Copyright (C) 2004 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This 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 3,
+ * or (at your option) any later version.
+ *
+ * This 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 file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xresource.h>
+
+#define TOP      60
+#define BOTTOM 3600
+
+#define TIMEOUT         5000 /* milliseconds */
+#define TIMEOUT_ONERROR 3333 /* milliseconds */
+
+static const char program_name[]= "xbatmon-simple";
+static int debug=-1, alarmlevel;
+
+/*---------- general utility stuff and declarations ----------*/
+
+static void fail(const char *m) {
+  fprintf(stderr,"error: %s\n", m);
+  exit(-1);
+}
+static void badusage(void) { fail("bad usage"); }
+
+typedef uint64_t value;
+#define VAL_NOTFOUND (~(value)0)
+
+typedef struct fileinfo fileinfo;
+typedef int parser(const fileinfo*);
+
+static parser parse_uevent;
+
+struct fileinfo {
+  const char *filename;
+  parser *parse;
+  const void *extra;
+};
+
+/*---------- structure of and results from /sys/class/power/... ----------*/
+/* variables this_... are the results from readbattery();
+ * if readbattery() succeeds the appropriate ones are all valid
+ * and not VAL_NOTFOUND
+ */
+
+typedef struct batinfo_field {
+  const char *label;
+  value *valuep;
+  const char *enumarray[10];
+} batinfo_field;
+
+#define BAT_QTYS(_, _ec, EC_, PC_)                             \
+  _(design_capacity##_ec,    BATTERY,  EC_##FULL_DESIGN )      \
+  _(last_full_capacity##_ec, BATTERY,  EC_##FULL        )      \
+  _(remaining_capacity##_ec, BATTERY,  EC_##NOW         )      \
+  _(present_rate##_ec,       BATTERY,  PC_##NOW         )
+ /* ENERGY [mWh]; POWER [mW]; CHARGE [uAh]; CURRENT [uA] */
+
+#define UEVENT_ESSENTIAL_QUANTITY_FIELDS(_)                    \
+  _(present,                 BATTERY,  PRESENT /* bool */ )    \
+  _(online,                  MAINS,    ONLINE  /* bool */ )
+
+#define UEVENT_FUNKY_QUANTITY_FIELDS(_)                \
+  BAT_QTYS(_,_energy,ENERGY_,POWER_)           \
+  BAT_QTYS(_,_charge,CHARGE_,CURRENT_)
+
+#define UEVENT_OPTIONAL_QUANTITY_FIELDS(_)                     \
+  _(voltage,                 BATTERY,  VOLTAGE_NOW /* uV */ )
+
+#define UEVENT_ENUM_FIELDS(_)                                          \
+  _(state,   BATTERY,  STATUS,  "Discharging","Charging","Full","Unknown" ) \
+  _(type,    BOTH,     TYPE,    "Mains",       "Battery"              )
+
+#define CHGST_DISCHARGING 0 /* Reflects order in _(state,...) above     */
+#define CHGST_CHARGING    1 /* Also, much code assumes exactly          */
+#define CHGST_CHARGED     2 /* these three possible states.             */
+#define CHGST_UNKNOWN     3 /* these three possible states.             */
+#define CHGST_ERROR       8 /* Except that this one is an extra bit.    */
+
+#define TYPE_MAINS        0 /* Reflects order in _(type,...) above        */
+#define TYPE_BATTERY      1 /* Also, much code assumes exactly these two  */
+#define TYPE_BOTH       100 /* Except this is a magic invalid value.      */
+
+#define SEPARATE_QUANTITY_FIELDS(_)            \
+  /* See commit ec6f5f0be800bc5f2a27046833dba04e0c67ffac for
+     the code needed to use this */
+
+
+#define ALL_DIRECT_VARS(_)                     \
+  UEVENT_ESSENTIAL_QUANTITY_FIELDS(_)          \
+  UEVENT_FUNKY_QUANTITY_FIELDS(_)              \
+  UEVENT_OPTIONAL_QUANTITY_FIELDS(_)           \
+  UEVENT_ENUM_FIELDS(_)                                \
+  SEPARATE_QUANTITY_FIELDS(_)
+
+#define ALL_VARS(_)                            \
+  ALL_DIRECT_VARS(_)                           \
+  BAT_QTYS(_,,,)
+
+#define ALL_NEEDED_FIELDS(_)                   \
+  UEVENT_ESSENTIAL_QUANTITY_FIELDS(_)          \
+  UEVENT_ENUM_FIELDS(_)                                \
+  SEPARATE_QUANTITY_FIELDS(_)
+
+#define ALL_PLAIN_ACCUMULATE_FIELDS(_)         \
+  UEVENT_ESSENTIAL_QUANTITY_FIELDS(_)          \
+  SEPARATE_QUANTITY_FIELDS(_)
+
+#define ALL_ACCUMULATE_FIELDS(_)               \
+  ALL_PLAIN_ACCUMULATE_FIELDS(_)               \
+  BAT_QTYS(_,,,)
+
+
+#define F_VAR(f,...) \
+static value this_##f;
+ALL_VARS(F_VAR)
+
+#define Q_FLD(f,t,l)        { "POWER_SUPPLY_" #l, &this_##f },
+#define E_FLD(f,t,l,vl...)  { "POWER_SUPPLY_" #l, &this_##f, { vl } },
+
+static const batinfo_field uevent_fields[]= {
+  UEVENT_ESSENTIAL_QUANTITY_FIELDS(Q_FLD)
+  UEVENT_FUNKY_QUANTITY_FIELDS(Q_FLD)
+  UEVENT_OPTIONAL_QUANTITY_FIELDS(Q_FLD)
+  UEVENT_ENUM_FIELDS(E_FLD)
+  { 0 }
+};
+
+#define S_FLD(f,t,fn,vl...)                                            \
+static const batinfo_field bif_##f = { 0, &this_##f, { vl } };
+  SEPARATE_QUANTITY_FIELDS(S_FLD)
+
+#define S_FILE(f,t,fn,vl...) { fn, parse_separate, &bif_##f },
+
+static const fileinfo files[]= {
+  { "uevent",  parse_uevent,  uevent_fields },
+  SEPARATE_QUANTITY_FIELDS(S_FILE)
+  { 0 }
+};
+
+/*---------- parsing of one thingx in /sys/class/power/... ----------*/
+
+/* variables private to the parser and its error handlers */
+static char batlinebuf[1000];
+static FILE *batfile;
+static const char *batdirname;
+static const char *batfilename;
+static const char *batlinevalue;
+
+static int batfailf(const char *why) {
+  if (batlinevalue) {
+    fprintf(stderr,"%s/%s: %s value `%s': %s\n",
+           batdirname,batfilename, batlinebuf,batlinevalue,why);
+  } else {
+    fprintf(stderr,"%s/%s: %s: `%s'\n",
+           batdirname,batfilename, why, batlinebuf);
+  }
+  return -1;
+}
+
+static int batfailc(const char *why) {
+  fprintf(stderr,"%s/%s: %s\n",
+         batdirname,batfilename, why);
+  return -1;
+}
+
+static int batfaile(const char *syscall, const char *target) {
+  fprintf(stderr,"%s: failed to %s %s: %s\n",
+         batdirname ? batdirname : "*", syscall, target, strerror(errno));
+  return -1;
+}
+
+static int chdir_base(void) {
+  int r;
+  
+  r= chdir("/sys/class/power_supply");
+  if (r) return batfaile("chdir","/sys/class/power_supply");
+
+  return 0;
+}
+
+static void tidybattery(void) {
+  if (batfile) { fclose(batfile); batfile=0; }
+}
+
+static int parse_value(const fileinfo *cfile, const batinfo_field *field) {
+  if (*field->valuep != VAL_NOTFOUND)
+    return batfailf("value specified multiple times");
+
+  if (!field->enumarray[0]) {
+
+    char *ep;
+    *field->valuep= strtoull(batlinevalue,&ep,10);
+    if (*ep)
+      batfailf("value number syntax incorrect");
+
+  } else {
+       
+    const char *const *enumsearch;
+    for (*field->valuep=0, enumsearch=field->enumarray;
+        *enumsearch && strcmp(*enumsearch,batlinevalue);
+        (*field->valuep)++, enumsearch++);
+    if (!*enumsearch)
+      batfailf("unknown enum value");
+
+  }
+  return 0;
+}
+
+static int parse_uevent(const fileinfo *cfile) {
+  char *equals= strchr(batlinebuf,'=');
+  if (!equals)
+    return batfailf("line without a equals");
+  *equals= 0;
+  batlinevalue = equals+1;
+
+  const batinfo_field *field;
+  for (field=cfile->extra; field->label; field++) {
+    if (!strcmp(field->label,batlinebuf))
+      goto found;
+  }
+  return 0;
+
+ found:
+  return parse_value(cfile, field);
+}
+
+static int readbattery(void) { /* 0=>ok, -1=>couldn't */
+  
+  const fileinfo *cfile;
+  char *sr;
+  int r, l;
+  
+  r= chdir_base();
+  if (r) return r;
+
+  r= chdir(batdirname);
+  if (r) return batfaile("chdir",batdirname);
+
+#define V_NOTFOUND(f,...) \
+  this_##f = VAL_NOTFOUND;
+ALL_VARS(V_NOTFOUND)
+
+  for (cfile=files;
+       (batfilename= cfile->filename);
+       cfile++) {
+    batfile= fopen(batfilename,"r");
+    if (!batfile) {
+      if (errno == ENOENT) continue;
+      return batfaile("open",batfilename);
+    }
+
+    for (;;) {
+      batlinevalue= 0;
+      
+      sr= fgets(batlinebuf,sizeof(batlinebuf),batfile);
+      if (ferror(batfile)) return batfaile("read",batfilename);
+      if (!sr && feof(batfile)) break;
+      l= strlen(batlinebuf);
+      assert(l>0);
+      if (batlinebuf[l-1] != '\n')
+       return batfailf("line too long");
+      batlinebuf[l-1]= 0;
+
+      if (cfile->parse(cfile))
+       return -1;
+    }
+
+    fclose(batfile);
+    batfile= 0;
+  }
+
+  if (debug) {
+    printf("%s:\n",batdirname);
+#define V_PRINT(f,...)                                 \
+    printf(" %-30s = %20"PRId64"\n", #f, (int64_t)this_##f);
+ALL_DIRECT_VARS(V_PRINT)
+  }
+
+  int needsfields_MAINS   = this_type == TYPE_MAINS;
+  int needsfields_BATTERY = this_type == TYPE_BATTERY;
+  int needsfields_BOTH    = 1;
+
+  int missing = 0;
+
+#define V_NEEDED(f,t,...)                              \
+  if (needsfields_##t && this_##f == VAL_NOTFOUND) {   \
+    fprintf(stderr,"%s: %s: not found\n",              \
+           batdirname, #f);                            \
+    missing++;                                         \
+  }
+ALL_NEEDED_FIELDS(V_NEEDED)
+
+  if (missing) return -1;
+
+  return 0;
+}   
+
+/*---------- data collection and analysis ----------*/
+
+/* These next three variables are the results of the charging state */
+static unsigned charging_mask; /* 1u<<CHGST_* | ... */
+static double nondegraded_norm, fill_norm, ratepersec_norm;
+static int alarmed;
+
+#define Q_VAR(f,t,...) \
+static double total_##f;
+  ALL_ACCUMULATE_FIELDS(Q_VAR)
+
+static void acquiredata(void) {
+  DIR *di = 0;
+  struct dirent *de;
+  int r;
+  
+  charging_mask= 0;
+  alarmed = 0;
+
+  if (debug) printf("\n");
+
+#define Q_ZERO(f,t,...) \
+  total_##f= 0;
+ALL_ACCUMULATE_FIELDS(Q_ZERO)
+
+  r = chdir_base();
+  if (r) goto bad;
+
+  di= opendir(".");  if (!di) { batfaile("opendir","battery"); goto bad; }
+  while ((de= readdir(di))) {
+    if (de->d_name[0]==0 || de->d_name[0]=='.') continue;
+
+    batdirname= de->d_name;
+    r= readbattery();
+    tidybattery();
+
+    if (r) {
+    bad:
+      charging_mask |= (1u << CHGST_ERROR);
+      break;
+    }
+
+    if (this_type == TYPE_BATTERY) {
+      if (!this_present)
+       continue;
+
+      charging_mask |= 1u << this_state;
+
+#define QTY_SUPPLIED(f,...)   this_##f != VAL_NOTFOUND &&
+#define QTY_USE_ENERGY(f,...) this_##f = this_##f##_energy;
+#define QTY_USE_CHARGE(f,...) this_##f = this_##f##_charge;
+
+      double funky_multiplier;
+      if (BAT_QTYS(QTY_SUPPLIED,_energy,,) 1) {
+       if (debug) printf(" using energy\n");
+       BAT_QTYS(QTY_USE_ENERGY,,,);
+       funky_multiplier = 1.0;
+      } else if (BAT_QTYS(QTY_SUPPLIED,_charge,,)
+                this_voltage != VAL_NOTFOUND) {
+       if (debug) printf(" using charge\n");
+       BAT_QTYS(QTY_USE_CHARGE,,,);
+       funky_multiplier = this_voltage * 1e-6;
+      } else {
+       batfailc("neither complete set of energy nor charge");
+       continue;
+      }
+      if (this_state == CHGST_DISCHARGING)
+       /* negate it */
+       total_present_rate -= 2.0 * this_present_rate * funky_multiplier;
+
+#define Q_ACCUMULATE_FUNKY(f,...)                      \
+      total_##f += this_##f * funky_multiplier;
+BAT_QTYS(Q_ACCUMULATE_FUNKY,,,)
+    }
+
+#define Q_ACCUMULATE_PLAIN(f,t,...)                    \
+    if (this_type == TYPE_##t)                 \
+      total_##f += this_##f;
+ALL_PLAIN_ACCUMULATE_FIELDS(Q_ACCUMULATE_PLAIN)
+
+      
+  }
+  if (di) closedir(di);
+
+  if (debug) {
+    printf("TOTAL:\n");
+    printf(" %-30s = %#20x\n", "mask", charging_mask);
+#define T_PRINT(f,...)                                 \
+    printf(" %-30s = %20.6f\n", #f, total_##f);
+BAT_QTYS(T_PRINT,,,)
+ALL_PLAIN_ACCUMULATE_FIELDS(T_PRINT)
+  }
+
+  if ((charging_mask & (1u<<CHGST_DISCHARGING)) &&
+      !total_online/*mains*/) {
+    double time_remaining =
+      -total_remaining_capacity * 3600.0 / total_present_rate;
+    if (debug) printf(" %-30s = %20.6f\n", "time remaining", time_remaining);
+    if (time_remaining < alarmlevel)
+      alarmed = 1;
+  }
+
+  if (total_design_capacity < 0.5)
+    total_design_capacity= 1.0;
+
+  if (total_last_full_capacity < total_remaining_capacity)
+    total_last_full_capacity= total_remaining_capacity;
+  if (total_design_capacity < total_last_full_capacity)
+    total_design_capacity= total_last_full_capacity;
+
+  nondegraded_norm= total_last_full_capacity / total_design_capacity;
+  fill_norm= total_remaining_capacity / total_design_capacity;
+  ratepersec_norm=  total_present_rate
+    / (3600.0 * total_design_capacity);
+}
+
+static void initacquire(void) {
+}  
+
+/*---------- argument parsing ----------*/
+
+#define COLOURS                                        \
+  C(blue,           discharging)               \
+  C(green,         charging)                   \
+  C(cyan,           charged)                   \
+  C(lightgrey,      notcharging)               \
+  C(grey,           confusing)                 \
+  C(black,          normal)                    \
+  C(red,            low)                       \
+  C(dimgrey,        degraded)                  \
+  C(darkgreen,      absent)                    \
+  C(yellow,         error)                     \
+  C(white,          equilibrium)               \
+  GC(remain)                                   \
+  GC(white)                                    \
+  GC(empty)
+
+static XrmDatabase xrm;
+static Display *disp;
+static int screen;
+static const char *parentwindow;
+
+static const char defaultresources[]=
+#define GC(g)
+#define C(c,u)                                 \
+  "*" #u "Color: " #c "\n"
+  COLOURS
+#undef GC
+#undef C
+  ;
+
+#define S(s) ((char*)(s))
+static const XrmOptionDescRec optiontable[]= {
+  { S("-debug"),        S("*debug"),        XrmoptionIsArg },
+  { S("-warningTime"),  S("*warningTime"),  XrmoptionSepArg },
+  { S("-display"),      S("*display"),      XrmoptionSepArg },
+  { S("-geometry"),     S("*geometry"),     XrmoptionSepArg },
+  { S("-into"),         S("*parentWindow"), XrmoptionSepArg },
+  { S("-iconic"),       S("*iconic"),       XrmoptionIsArg },
+  { S("-withdrawn"),    S("*withdrawn"),    XrmoptionIsArg },
+#define GC(g)
+#define C(c,u)                                                 \
+  { S("-" #u "Color"),  S("*" #u "Color"),  XrmoptionSepArg }, \
+  { S("-" #u "Colour"), S("*" #u "Color"),  XrmoptionSepArg },
+  COLOURS
+#undef GC
+#undef C
+};
+
+static const char *getresource(const char *want) {
+  char name_buf[256], class_buf[256];
+  XrmValue val;
+  char *rep_type_dummy;
+  int r;
+
+  assert(strlen(want) < 128);
+
+  sprintf(name_buf,"xbatmon-simple.%s",want);
+  sprintf(class_buf,"Xbatmon-Simple.%s",want);
+  
+  r= XrmGetResource(xrm, name_buf,class_buf, &rep_type_dummy, &val);
+  if (r) return val.addr;
+
+  sprintf(name_buf,"xacpi-simple.%s",want);
+  sprintf(class_buf,"Xacpi-Simple.%s",want);
+  
+  r= XrmGetResource(xrm, name_buf,class_buf, &rep_type_dummy, &val);
+  if (r) return val.addr;
+  
+  return 0;
+}
+
+static int getresource_bool(const char *want, int def, int *cache) {
+  /* *cache should be initialised to -1 and will be set to !!value
+   * alternatively cache==0 is allowed */
+
+  if (cache && *cache >= 0) return *cache;
+
+  const char *str= getresource(want);
+  int result = def;
+  if (str && str[0]) {
+    char *ep;
+    long l= strtol(str,&ep,0);
+    if (!*ep) {
+      result = l > 0;
+    } else {
+      switch (str[0]) {
+      case 't': case 'T': case 'y': case 'Y':         result= 1;  break;
+      case 'f': case 'F': case 'n': case 'N':         result= 0;  break;
+      case '-': /* option name from XrmoptionIsArg */ result= 1;  break;
+      }
+    }
+  }
+
+  if (cache) *cache= result;
+  return result;
+}
+
+static void more_resources(const char *str, const char *why) {
+  XrmDatabase more;
+
+  if (!str) return;
+
+  more= XrmGetStringDatabase((char*)str);
+  if (!more) fail(why);
+  XrmCombineDatabase(more,&xrm,0);
+}
+
+static void parseargs(int argc, char **argv) {
+  Screen *screenscreen;
+  
+  XrmInitialize();
+
+  XrmParseCommand(&xrm, (XrmOptionDescRec*)optiontable,
+                 sizeof(optiontable)/sizeof(*optiontable),
+                 program_name, &argc, argv);
+
+  if (argc>1) badusage();
+
+  getresource_bool("debug",0,&debug);
+
+  const char *alarmlevel_string= getresource("alarmLevel");
+  alarmlevel = alarmlevel_string ? atoi(alarmlevel_string) : 300;
+
+  parentwindow = getresource("parentWindow");
+
+  disp= XOpenDisplay(getresource("display"));
+  if (!disp) fail("could not open display");
+
+  screen= DefaultScreen(disp);
+
+  screenscreen= ScreenOfDisplay(disp,screen);
+  if (!screenscreen) fail("screenofdisplay");
+  more_resources(XScreenResourceString(screenscreen), "screen resources");
+  more_resources(XResourceManagerString(disp), "display resources");
+  more_resources(defaultresources, "default resources");
+} 
+
+/*---------- display ----------*/
+
+static Window win;
+static int width, height;
+static Colormap cmap;
+static unsigned long lastbackground;
+
+typedef struct {
+  GC gc;
+  unsigned long lastfg;
+} Gcstate;
+
+#define C(c,u) static unsigned long pix_##u;
+#define GC(g) static Gcstate gc_##g;
+  COLOURS
+#undef C
+#undef GC
+
+static void refresh(void);
+
+#define CHGMASK_CHG_DIS ((1u<<CHGST_CHARGING) | (1u<<CHGST_DISCHARGING))
+
+static void failr(const char *m, int r) {
+  fprintf(stderr,"error: %s (code %d)\n", m, r);
+  exit(-1);
+}
+
+static void setbackground(unsigned long newbg) {
+  int r;
+  
+  if (newbg == lastbackground) return;
+  r= XSetWindowBackground(disp,win,newbg);
+  if (!r) fail("XSetWindowBackground");
+  lastbackground= newbg;
+}
+
+static void setforeground(Gcstate *g, unsigned long px) {
+  XGCValues gcv;
+  int r;
+  
+  if (g->lastfg == px) return;
+  
+  memset(&gcv,0,sizeof(gcv));
+  g->lastfg= gcv.foreground= px;
+  r= XChangeGC(disp,g->gc,GCForeground,&gcv);
+  if (!r) fail("XChangeGC");
+}
+
+static void show_solid(unsigned long px) {
+  setbackground(px);
+  XClearWindow(disp,win);
+}
+
+static void show(void) {
+  double elap, then;
+  int i, leftmost_lit, leftmost_nondeg, beyond, first_beyond;
+
+  if (!charging_mask)
+    return show_solid(pix_absent);
+
+  if (charging_mask & (1u << CHGST_ERROR))
+    return show_solid(pix_error);
+
+  setbackground(pix_degraded);
+  XClearWindow(disp,win);
+  
+  setforeground(&gc_remain,
+               !(charging_mask & CHGMASK_CHG_DIS) ?
+               (~charging_mask & (1u << CHGST_CHARGED) ?
+                pix_notcharging : pix_charged) :
+               !(~charging_mask & CHGMASK_CHG_DIS) ? pix_confusing :
+               charging_mask & (1u<<CHGST_CHARGING)
+               ? pix_charging : pix_discharging);
+               
+  setforeground(&gc_empty, alarmed ? pix_low : pix_normal);
+
+  for (i=0, first_beyond=1; i<height; i++) {
+    elap= !i ? 0 :
+      height==2 ? BOTTOM :
+      TOP * exp( (double)i / (height-2) * log( (double)BOTTOM/TOP ) );
+    
+    then= fill_norm + ratepersec_norm * elap;
+
+    beyond=
+      ((charging_mask & (1u<<CHGST_DISCHARGING) && then <= 0.0) ||
+       (charging_mask & (1u<<CHGST_CHARGING) && then>=nondegraded_norm));
+
+    if (then <= 0.0) then= 0.0;
+    else if (then >= nondegraded_norm) then= nondegraded_norm;
+
+    leftmost_lit= width * then;
+    leftmost_nondeg= width * nondegraded_norm;
+
+    if (beyond && first_beyond) {
+      XDrawLine(disp, win, gc_white.gc, 0,i, leftmost_nondeg,i);
+      first_beyond= 0;
+    } else {
+      if (leftmost_lit < leftmost_nondeg)
+       XDrawLine(disp, win, gc_empty.gc,
+                 leftmost_lit,i, leftmost_nondeg,i);
+      if (leftmost_lit >= 0)
+       XDrawLine(disp, win, gc_remain.gc, 0,i, leftmost_lit,i);
+    }
+  }
+}
+
+static void initgc(Gcstate *gc_r) {
+  XGCValues gcv;
+
+  memset(&gcv,0,sizeof(gcv));
+  gcv.function= GXcopy;
+  gcv.line_width= 1;
+  gc_r->lastfg= gcv.foreground= pix_equilibrium;
+  gc_r->gc= XCreateGC(disp,win, GCFunction|GCLineWidth|GCForeground, &gcv);
+}
+
+static void colour(unsigned long *pix_r, const char *whichcolour) {
+  XColor xc;
+  const char *name;
+  Status st;
+
+  name= getresource(whichcolour);
+  if (!name) fail("get colour resource");
+  
+  st= XAllocNamedColor(disp,cmap,name,&xc,&xc);
+  if (!st) fail(name);
+  
+  *pix_r= xc.pixel;
+}
+
+static void initgraphics(int argc, char **argv) {
+  int xwmgr, r;
+  const char *geom_string;
+  XSizeHints *normal_hints;
+  XWMHints *wm_hints;
+  XClassHint *class_hint;
+  int pos_x, pos_y, gravity;
+  char *program_name_silly;
+  
+  program_name_silly= (char*)program_name;
+
+  normal_hints= XAllocSizeHints();
+  wm_hints= XAllocWMHints();
+  class_hint= XAllocClassHint();
+
+  if (!normal_hints || !wm_hints || !class_hint)
+    fail("could not alloc hint(s)");
+
+  geom_string= getresource("geometry");
+
+  xwmgr= XWMGeometry(disp,screen, geom_string,"128x32", 0,
+                normal_hints,
+                &pos_x, &pos_y,
+                &width, &height,
+                &gravity);
+
+  unsigned long parentwindowid;
+  if (parentwindow)
+    parentwindowid = strtoul(parentwindow,0,0);
+  else
+    parentwindowid = DefaultRootWindow(disp);
+
+  win= XCreateSimpleWindow(disp,parentwindowid,
+                          pos_x,pos_y,width,height,0,0,0);
+  cmap= DefaultColormap(disp,screen);
+  
+#define C(c,u) colour(&pix_##u, #u "Color");
+#define GC(g) initgc(&gc_##g);
+  COLOURS
+#undef C
+#undef GC
+
+  r= XSetWindowBackground(disp,win,pix_degraded);
+  if (!r) fail("init set background");
+  lastbackground= pix_degraded;
+
+  normal_hints->flags= PWinGravity;
+  normal_hints->win_gravity= gravity;
+  normal_hints->x= pos_x;
+  normal_hints->y= pos_y;
+  normal_hints->width= width;
+  normal_hints->height= height;
+  if ((xwmgr & XValue) || (xwmgr & YValue))
+    normal_hints->flags |= USPosition;
+
+  wm_hints->flags= InputHint;
+  wm_hints->input= False;
+  wm_hints->initial_state=
+    (getresource_bool("withdrawn",0,0) ? WithdrawnState :
+     getresource_bool("iconic",0,0) ? IconicState
+     : NormalState);
+
+  class_hint->res_name= program_name_silly;
+  class_hint->res_class= program_name_silly;
+
+  XmbSetWMProperties(disp,win, program_name,program_name,
+                    argv,argc, normal_hints, wm_hints, class_hint);
+
+  XSelectInput(disp,win, ExposureMask|StructureNotifyMask);
+  XMapWindow(disp,win);
+}
+static void refresh(void) {
+  acquiredata();
+  show();
+}
+
+static void newgeometry(void) {
+  int dummy;
+  Window dummyw;
+  
+  XGetGeometry(disp,win, &dummyw,&dummy,&dummy, &width,&height, &dummy,&dummy);
+}
+
+static void eventloop(void) {
+  XEvent ev;
+  struct pollfd pfd;
+  int r, timeout;
+  
+  newgeometry();
+  refresh();
+
+  for (;;) {
+    XFlush(disp);
+
+    pfd.fd= ConnectionNumber(disp);
+    pfd.events= POLLIN|POLLERR;
+
+    timeout= !(charging_mask & (1u << CHGST_ERROR)) ? TIMEOUT : TIMEOUT_ONERROR;
+    r= poll(&pfd,1,timeout);
+    if (r==-1 && errno!=EINTR) failr("poll",errno);
+
+    while (XPending(disp)) {
+      XNextEvent(disp,&ev);
+      if (ev.type == ConfigureNotify) {
+       XConfigureEvent *ce= (void*)&ev;
+       width= ce->width;
+       height= ce->height;
+      }
+    }
+    refresh();
+  }
+}
+
+int main(int argc, char **argv) {
+  parseargs(argc,argv);
+  initacquire();
+  initgraphics(argc,argv);
+  eventloop();
+  return 0;
+}
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..02b4972
--- /dev/null
@@ -0,0 +1,500 @@
+chiark-utils (4.2.0) unstable; urgency=low
+
+  * Rename `xacpi-simple' to `xbatmon-simple':
+    - rename source file
+    - change program name in Makefiles, .gitignore, rules, etc.
+    - change program's idea of its own name for usage message
+    - look for X resources under both old ane new names
+    - provide a compatibility symlink (using dh_link)
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 10 Jun 2012 21:40:32 +0100
+
+chiark-utils (4.1.32) unstable; urgency=medium
+
+  xacpi-simple improvements:
+  * Better parsing of boolean -debug option.
+  * Support -into, -iconic and -withdrawn options.
+
+  Packaging fix:
+  * debian/rules clean removes various sv-* and dh log files.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 07 Jun 2012 19:22:46 +0100
+
+chiark-utils (4.1.31) unstable; urgency=medium
+
+  Bugfixes to programs:
+  * xacpi-simple updated:
+    - Now works with recent kernels by reading /sys/class/power_supply
+      (so is now misnamed, since that may or may not come from acpi).
+      Closes: #635242.
+    - Copes both with batteries which report charge/current and ones which
+      report energy/power.
+    - Copes with batteries which are not full, charging or discharging
+      (eg, if the system is on a/c but battery charging is suppressed).
+    - Shows red when time remaining is less than 5 minutes (configurable)
+      rather than trying to use "alarm" value from /sys/class/power_supply
+      (which is not very predictable or reliable).
+    - Option -debug produces some debugging output.
+    - In the code, rename pix_... variables to be named after the meaning
+      rather than the default colour.
+  * chiark-backup's /etc/chiark-backup/snap/nosnap is now more careful
+    about rm vs. rmdir of its link/mountpoint/whatever.
+
+  Documentation and description improvements:
+  * watershed now installs its head doc comment in
+    /usr/share/doc/chiark-utils-bin/watershed.txt.
+    This is apropos of #659989 but is of course not a proper fix.
+  * Mention rcopy-repeatedly in the Description.
+
+  User-visible packaging improvements:
+  * chiark-backup Suggests chiark-utils-bin, not the nonexistent
+    chiark-cprogs (for `summer').
+
+  Other packaging and source code improvements:
+  * Provide build-arch and build-indep debian/rules targets.
+  * Fix the type of a callback function passed to scandir, to expect
+    struct dirent**'s rather than void*'s.
+  * Add -Wno-pointer-sign to gcc warning options.
+  * Add ${misc:Depends} to Depends: lines.  Causes no change to the .debs.
+  * Switch to git.  Move .cvsignores to .gitignore, etc.
+  * Update my email address.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Wed, 06 Jun 2012 02:30:50 +0100
+
+chiark-utils (4.1.30) unstable; urgency=low
+
+  * rcopy-repeatedly: new utility
+  * cvs-repomove: work with Solaris's shoddy sed.  (Closes: #497670)
+  * backup/snap-drop: honour argument specifying vardir.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed,  8 Oct 2008 21:42:17 +0100
+
+chiark-utils (4.1.29) unstable; urgency=low
+
+  * backup-snaprsync: pass $vardir as argument to snap-drop
+  * backup-snaprsync: add a missing x prefix to an ssh invocation
+  * chiark-backup: really cope properly with nosnap removal
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 28 Aug 2008 20:02:47 +0100
+
+chiark-utils (4.1.28+nmu2) unstable; urgency=low
+
+  * Non-maintainer upload.
+  * Fix "file-in-etc-not-marked-as-conffile /etc/chiark-backup/snap/nosnap"
+    by adding /etc/chiark-backup/snap/nosnap to debian/chiark-
+    backup/conffiles (closes: #553560).
+
+ -- gregor herrmann <gregoa@debian.org>  Sat, 05 Dec 2009 13:27:32 +0100
+
+chiark-utils (4.1.28+nmu1) unstable; urgency=low
+
+  * Non-maintainer upload.
+  * change build dependency from libnettle-dev to nettle-dev
+    (Closes: #543123).
+
+ -- Magnus Holmgren <holmgren@debian.org>  Wed, 02 Sep 2009 08:55:36 +0200
+
+chiark-utils (4.1.28) unstable; urgency=high
+
+  chiark-named-conf:
+  * Remove obsolete +nodebug option from calls to dig;
+    required for BIND9 compatibility.  Thanks to Ross Younger.
+  * Foreign zones work properly using the configuration
+    prevailing at the end of the config file.
+  * Warn if user specifies a zone with the trailing `.'.
+
+  Other:
+  * hexterm: Declare and document the dependency of hexterm on tcl8.4.
+  * chiark-backup: remove snap-mount properly whether it's a dir or a leaf
+    node (such as a symlink).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed, 27 Aug 2008 23:26:55 +0100
+
+chiark-utils (4.1.24) unstable; urgency=low
+
+  * Change my email address in the Maintainer field so that
+    when I say Closes: in the changelog it doesn't say "fixed in NMU".
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 23 Sep 2007 16:39:48 +0100
+
+chiark-utils (4.1.23) unstable; urgency=low
+
+  * chiark-named-conf improvements:
+    - set $|=1 to make output better with eg  2>&1 |less
+    - new allow-indirect-glue directive
+    - new forbid-slave directive
+    - remove incorrect references to SOA ORIGIN - should be MNAME
+    - allow config file to not exist.  Closes: #274528.
+    - do not warn about glueful nameservers in serverless-glueless
+      areas (since these can't cause problems).  Thanks to Ben Harris
+      for report and subsequent discussion.
+  * chiark-backup Depends on chiark-utils-bin.  Closes: #401611.
+  * summer manpage (thanks to Peter Maydell for initial contribution).
+    (Closes: #401640.)
+  * summer has new -B option for suppressing times of unusual node types.
+  * backup-snaprsync passes -B to summer.
+  * copyright notices for various scripts.
+  * many scripts mentioned in Description and debian/copyright.
+  * use dh_strip rather than doing it ourselves.  Closes #436624.
+  (4.1.22 was an internal unreleased version.)
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat, 22 Sep 2007 02:29:55 +0100
+
+chiark-utils (4.1.21) unstable; urgency=low
+
+  * backup-snaprsync: pass -I to rsync when copying rsums.
+  * update licence to GPLv3 or later (was GPLv2 or later).
+  * remove FSF's paper address from debian/copyright
+    and refer to the FSF and GNU websites instead.
+  * update copyright dates in backup/.
+  * Fix some out of date email addresses.
+  * summer and watershed's shlibdeps go into Recommends since
+    bits of the package are useable without them.
+  * seddery libgmp3 dependencies to libgmp3 | libgmp3c2 since
+    we are C and not C++ and care nothing for C++ ABI transitions.
+  * Document the dependency relationships in the Description.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Fri, 21 Sep 2007 21:58:33 +0100
+
+chiark-utils (4.1.20) unstable; urgency=low
+
+  * Fix bashism in upstream backup/Makefile.  Closes: #379541.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Fri, 13 Jul 2007 09:49:17 +0100
+
+chiark-utils (4.1.19) unstable; urgency=low
+
+  * Mention summer, xacpi-simple, watershed in chiark-utils-bin's description.
+  * Set SHELL=/bin/bash in debian/rules.  Closes: #379541.
+  * Remove Matthew from the Uploaders.  Closes: #280012.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 12 Jul 2007 17:25:11 +0100
+
+chiark-utils (4.1.18) unstable; urgency=low
+
+  * summer works even if passed multiple option-containing args.
+  * workaround for nettle bug, supply -lgmp if -lnettle.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed, 23 May 2007 19:42:05 +0100
+
+chiark-utils (4.1.17) unstable; urgency=low
+
+  * summer has new -M (do not print mtimes) option.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed, 23 May 2007 18:56:53 +0100
+
+chiark-utils (4.1.16) unstable; urgency=low
+
+  * chiark-backup's snaprsync has new rsynccompress option.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 19 Apr 2007 23:23:32 +0100
+
+chiark-utils (4.1.15) unstable; urgency=low
+
+  * chiark-backup's snaprsync sedderies mountpoint out before sums diff.
+  * chiark-backup's snaprsync has new sshopts option.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 19 Apr 2007 20:12:37 +0100
+
+chiark-utils (4.1.14) unstable; urgency=low
+
+  * summer has new -x (one file system) option.
+  * summer produces better output with -f when scandir fails.
+  * chiark-backup's snaprsync uses summer -x.
+  * chiark-backup suggests chiark-cprogs with summer with -x.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 18 Feb 2007 13:02:21 +0000
+
+chiark-utils (4.1.13) unstable; urgency=low
+
+  * New `nosnap' no-op snap kind.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Fri, 16 Feb 2007 19:10:23 +0000
+
+chiark-utils (4.1.12) unstable; urgency=low
+
+  * New `watershed' program.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 15 Feb 2007 01:12:05 +0000
+
+chiark-utils (4.1.11) unstable; urgency=low
+
+  * remountresizereiserfs version copied from chiark.  Works with LVM1
+    on 2.6.x, but not with 2.4.x due to stupid stupid LVM bug where
+    sizes are reported in 512-byte kilobytes but only sometimes.  Also
+    fixed to cope better with dm-based LVM volumes.
+  * New script `summarise-mailbox-preserving-privacy'.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed, 22 Nov 2006 19:53:17 +0000
+
+chiark-utils (4.1.10) unstable; urgency=low
+
+  * New script `hexterm', `terminal emulator' for binary data,
+    display/entry in ASCII and hex.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Mon,  2 Oct 2006 22:21:52 +0100
+
+chiark-utils (4.1.9) unstable; urgency=low
+
+  * New better algorithm for expire-iso8601.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Mon,  2 Oct 2006 18:11:56 +0100
+
+chiark-utils (4.1.8) unstable; urgency=low
+
+  * remountresizereiserfs: New script for calculating resize= parameter.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Tue, 15 Aug 2006 15:46:19 +0100
+
+chiark-utils (4.1.7) unstable; urgency=low
+
+  * backup: new `noinc' fs line option.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Mon, 14 Aug 2006 11:04:30 +0100
+
+chiark-utils (4.1.6) unstable; urgency=low
+
+  * backup/snaprsync: work around bug in bash (Debian #382798).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Mon, 14 Aug 2006 00:43:00 +0100
+
+chiark-utils (4.1.5) unstable; urgency=low
+
+  * expire-iso8601 - new script.
+  * backup/snaprsync: allow setting of program to use for summer; allow
+    setting of rsync options; allow settings to be set to empty strings on
+    the command line; do not fail if localprevious,rsums is missing.
+  * cprogs/summer.c: properly fix for reentrancy of buf.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 13 Aug 2006 17:04:08 +0100
+
+chiark-utils (4.1.4) unstable; urgency=low
+
+  summer bugfix:
+  * `buf' is used reentrantely by recurse() and sometimes is the `path'
+    argument to recurse; cope appropriately.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat,  5 Aug 2006 15:34:03 +0100
+
+chiark-utils (4.1.3) unstable; urgency=low
+
+  backup snap remountrocp fixes:
+  * Size of snapshot volume properly calculated (now we divide by 1k
+    since df reports in bytes and vgdisplay in kbytes).
+  * Restrict copy to same file system (oops!).
+  * Nicer messages from remountrocp (-q to mkfs; various echoes).
+
+  backup snaprsync fixes:
+  * Support --subdir= option, defaults to `.' (root of source fs).
+  * Correctly set up /var/lib/chiark-backup/snap-drop so drop works.
+  * Send output of local summer to fd 3 as required.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 30 Jul 2006 15:41:07 +0100
+
+chiark-utils (4.1.2) unstable; urgency=low
+
+  * Translate /dev/mapper devices from mount table into LVM device names.
+  * Use `current logical extents associated to logical volume' (field 8)
+    rather than `allocated logical extents of logical volume' (field 9)
+    for size, as the latter is sometimes -1 for some reason.
+  * Attempt 10 times to mount readonly.
+  * Comments stating that lvm{create,extents}core1 need vgroup.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 30 Jul 2006 14:44:23 +0100
+
+chiark-utils (4.1.1) unstable; urgency=low
+
+  * summer sorts the output and identifies hardlink
+    targets instead of printing link count.
+  * backup: new `remountrocp' snapshot type.
+  * backup: snaprsync script shipped.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 30 Jul 2006 13:19:27 +0100
+
+chiark-utils (4.1.0) unstable; urgency=low
+
+  New facilities:
+  * New `cvs-repomove' and `cvs-adjustroot' programs,
+        for moving CVS repositories about safely and easily.
+  * New `random-word' script (no documentation).
+  * New `xacpi-simple' utility (no documentation).
+  * New `summer' checksumming program with no manpage.
+  * Spice and gnuplot scripts `ngspice2genspic', `gnucap2genspec'
+    `genspic2gnuplot'.
+
+  chiark-backup:
+  * Pass -P option to df (to make new df not break things).
+  * Pass -xtmpfs option to df.
+  * Fix critical driver script lost output bug.
+  * Fix pipe mode by passing -m600 when creating it.
+  * Don't mind devices on local filesystems (was failed stat bug).
+  * New lvm/remount-ro snapshotting feature.
+  * New `snaprsync' script which is still a work in progress.
+  * New gtar backup type.
+  * Use (set) dump label field.
+  * Say `(C)' in nroff pages rather than `\(c0' (typo for `\(co').
+    (Closes: #208299 - Debian bug, thanks to Colin Watson.)
+  * Move tape.* symlinks for chiark example into chiark directory.
+    (Closes: #208300 - Debian bug, thanks to Colin Watson.)
+
+  Other fixes and improvements:
+  * Size for readbuffer/writebuffer may be in k or bytes.
+  * sync-accounts works properly with groups with - + . in names
+  * really has -R (chroot) option, and usage message improved.
+  * Typo in copyright statement in with-lock-ex.1 fixed.
+  * Mention cvs-* and palm-datebook-reminders in debian/control.
+  * Make myself (Ian Jackson) the Maintainer.
+  * Fix manpage titles for palm-datebook-reminders(1) and really(8).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Tue, 27 Jun 2006 19:40:57 +0100
+
+chiark-utils (4.0.0) unstable; urgency=medium
+
+  New programs and utilities:
+  * trivsoundd (cprogs, no .deb).  Largely untested.
+  * palm-datebook-reminders (scripts, chiark-scripts.deb)
+  * really (cprogs, chiark-really.deb)
+  * with-lock-ex (cprogs, chiark-utils-bin.deb)
+  * usernice, cvsweb-list, smtpallow.  (Source only, not built.)
+    
+  chiark-named-conf:
+  * remove warnings by adding complete set of function prototypes
+  * check foreign zones from delegation by default (-mforeign!*)
+
+  chiark-rwbuffer:
+  * fix writebuffer's progname.
+
+  chiark-backup:
+  * full dumps have tapedesc in TAPEID
+  * fix reporting of unaccountable filesystems.
+  * compression support (gz as a style option).
+  * ntfsimage support.
+  * Count and display on-tape file numbers.
+  * Count output blocks for each tape file and in total.
+  * $nice and $nasty in settings.pl.
+  * Show mt runes when we execute them.
+  * A little less clone-and-hack.
+  * Manpages.
+
+  Build system improvements:
+  * Make manpages non-executable (!) (ie install with INSTALL_SHARE)
+  * Makefile commonality moved into settings.make.
+  * readbuffer/writebuffer moved into new cprogs directory.
+  
+  Debian-specific changes:
+  * sync-accounts and chiark-named-conf merged into single
+    chiark-scripts package.
+  * Add section and priority fields
+  * Update to a sensible standards-version
+  * Correct location of the GPL
+
+ -- Matthew Vernon <matthew@debian.org>  Wed, 16 Jul 2003 12:01:01 +0100
+
+chiark-utils (3.0.3) unstable; urgency=low
+
+  * chiark-backup: compatibility with md5sum from dpkg 1.10.4.
+  * chiark-named-conf: manpage gluelessness section improved.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 20 Oct 2002 17:42:53 +0100
+
+chiark-utils (3.0.2) unstable; urgency=low
+
+  * Enhanced portability: cope with (and preserve) comments and 
+    blank lines in local database files.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 14 Jul 2002 23:21:31 +0100
+
+chiark-utils (3.0.1) unstable; urgency=low
+
+  * Enhanced portability: -g root => -g $(SYSTEM_GROUP)
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 14 Jul 2002 21:52:46 +0100
+
+chiark-utils (3.0.0) unstable; urgency=low
+
+  sync-accounts:
+  * Moved all files from old CVS repository into chiark-utils.
+  * Manpages for everything.
+  * /etc/sync-accounts comments may be indented.
+  * Copyright notices sorted out.
+  * sync-accounts.linux uses vipw, vigr.
+  * Removed sync-accounts-mktar (eewwgh).
+  * Makefile and Debian control stuff added.
+
+  Other changes:
+  * debian/copyright file improved.
+  * named-conf script has copyright notice at top.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 14 Jul 2002 21:18:35 +0100
+
+chiark-utils (2.2.6) unstable; urgency=low
+
+  * chiark-named-conf --mail-* works without zone list as args.
+  * chiark-named-conf --mail-* prints better output.
+
+ -- Ian Jackson <ian@chiark.greenend.org.uk>  Sat,  1 Jun 2002 01:12:00 +0100
+
+chiark-utils (2.2.4) unstable; urgency=low
+
+  * chiark-named-conf can send mail reports, and is generally better.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 26 May 2002 21:03:11 +0100
+
+chiark-utils (2.2.3) unstable; urgency=low
+
+  * Allow invocation on 2nd-level domains by coping with superzone `.'
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 27 Jan 2002 19:38:14 +0000
+
+chiark-utils (2.2.2) unstable; urgency=low
+
+  * chiark-named-conf does checks on all slaves even for `*' zones.
+  * chiark-named-conf glueless-serverless works properly.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat, 12 Jan 2002 20:19:32 +0000
+
+chiark-utils (2.2.1) unstable; urgency=low
+
+  * chiark-named-conf gets list of delegated servers right when doing
+    simple checks (ie -l).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat, 12 Jan 2002 00:58:22 +0000
+
+chiark-utils (2.2.0) unstable; urgency=low
+
+  * Add new chiark-named-conf script with manpage etc., and package it.
+  * Add new scripts directory with Makefile.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat, 12 Jan 2002 00:47:10 +0000
+
+chiark-utils (2.1.1) unstable; urgency=low
+
+  * add call to mt reten to full.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed, 28 Nov 2001 00:05:11 +0000
+
+chiark-utils (2.1.0) experimental; urgency=low
+
+  * man pages for readbuffer, writebuffer from Richard Kettlewell.
+  * add info re last-tape and checkallused to iwjbackup.txt.
+  * new `backup-labeltape' utility.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Fri,  9 Nov 2001 21:12:25 +0000
+
+chiark-utils (2.0.0) experimental; urgency=low
+  
+  * Initial prepackaged release of chiark backup.
+
+  Significant changes since merge chiark/anarres/mnemeth:
+  * readbuffer/writebuffer command line arg for buffer size.
+  * readbuffer/writebuffer share common code.
+  * example config files from Relativity and chiark shipped.
+  * Improved documentation.
+  * bringup-hook created.
+  * bugfixes (all of bugs introduced by merge, AFAICT).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Mon,  8 Oct 2001 01:52:21 +0100
+
+# Local variables:
+# mode: debian-changelog
+# End:
diff --git a/debian/chiark-backup/conffiles b/debian/chiark-backup/conffiles
new file mode 100644 (file)
index 0000000..c38afbf
--- /dev/null
@@ -0,0 +1,5 @@
+/etc/chiark-backup/settings.sh
+/etc/chiark-backup/snap/lvm
+/etc/chiark-backup/snap/remount
+/etc/chiark-backup/snap/remountrocp
+/etc/chiark-backup/snap/nosnap
diff --git a/debian/chiark-utils-bin.links b/debian/chiark-utils-bin.links
new file mode 100644 (file)
index 0000000..becff8f
--- /dev/null
@@ -0,0 +1 @@
+/usr/bin/xbatmon-simple /usr/bin/xacpi-simple
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7ed6ff8
--- /dev/null
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..f99a656
--- /dev/null
@@ -0,0 +1,130 @@
+Source: chiark-utils
+Section: admin
+Priority: extra
+Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
+Build-Depends: libx11-dev, nettle-dev, debhelper (>= 5)
+Standards-Version: 3.9.1
+
+Package: chiark-backup
+Section: utils
+Priority: extra
+Architecture: all
+Depends: chiark-rwbuffer, chiark-utils-bin, ${misc:Depends}
+Suggests: chiark-utils-bin (>= 4.1.14)
+Description: backup system for small systems and networks
+ These are the backup scripts used by chiark.greenend.org.uk and other
+ systems belonging to the Sinister Greenend Organisation.  Features:
+  * Suitable for single systems and small networks.
+  * Reasonably simple; they do what you tell it to.
+  * Hard failures when individual systems fail, to encourage fixing !
+ If you have a larger site you may wish to look at Amanda.
+
+Package: chiark-scripts
+Section: admin
+Priority: extra
+Conflicts: chiark-named-conf, sync-accounts
+Replaces: chiark-named-conf, sync-accounts
+Depends: ${misc:Depends}
+Suggests: tcl8.4
+Architecture: all
+Description: chiark system administration scripts
+ This package contains a number of small administration scripts used
+ by chiark.greenend.org.uk and other systems belonging to the Sinister
+ Greenend Organisation.  Featuring:
+ .
+ chiark-named-conf: a tool for managing nameserver configurations
+ and checking for suspected DNS problems.  Its main functions are to
+ check that delegations are appropriate and working, that secondary
+ zones are slaved from the right places, and to generate a
+ configuration for BIND, from its own input file.
+ .
+ sync-accounts: a simple but flexible account info synchroniser.
+ sync-accounts is a tool for copying un*x account data from remote
+ systems and installing it locally.  It is flexible and reasonably
+ straightforward, but lacks integration with other distributed
+ databases such as NIS.
+ .
+ cvs-repomove and cvs-adjustroot: tools for moving CVS repositories
+ and adjusting working trees.
+ .
+ palm-datebook-reminders: a program which emails mails you reminders
+ about the appointments in your Palm's Datebook.
+ .
+ cvsweb-list: cgi program to list ucgi (userv-utils) cvsweb repos
+ .
+ expire-iso8601: keep or expire backup trees named after their dates
+ .
+ gnucap2genspic, ngspice2genspic, genspic2gnuplot: convert gnucap
+ files and ngspice output files to genspic and genspic files to
+ gnuplot input so they can be plotted.
+ .
+ hexterm: connects to serial port and allows the user interact in
+ ASCII and hex.  Ie, a hex "terminal" program which lets you speak a
+ serial port protocol directly.  (Needs tcl8.4 to be installed.)
+ .
+ random-word, remountresizereiserfs,
+ summarise-mailbox-preserving-privacy
+
+Package: chiark-rwbuffer
+Section: utils
+Priority: extra
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: readbuffer/writebuffer: prevents tape drive seesawing, etc.
+ readbuffer and writebuffer: programs for reading input from devices,
+ and writing output to, which don't like constant stopping and
+ starting, such as tape drives and audio playback devices.
+
+Package: chiark-utils-bin
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Recommends: ${shlibs:Recommends}
+Suggests: ${shlibs:Suggests}
+Section: utils
+Priority: extra
+Description: chiark system administration utilities
+ This package contains a number of small administration scripts used
+ by chiark.greenend.org.uk and other systems belonging to the Sinister
+ Greenend Organisation.  Currently featuring only:
+ .
+ with-lock-ex: a simple tool for acquiring a lockfile before running
+ another program or script.
+ .
+ summer: a tool for reporting complete details about a filesystem tree
+ in a parseable format, including checksums.
+ .
+ xbatmon-simple: a very simple X client for displaying battery
+ charge status.
+ .
+ watershed: a utility for saving on superfluous executions of an
+ idempotent command.  (This is the same utility as shipped separately
+ in Ubuntu's udev, but with slightly different defaults and a
+ different install location.)
+ .
+ rcopy-repeatedly: a utility for repeatedly copying a file from one
+ host to another, to keep a copy constantly up to date.
+ .
+ summer and watershed require the installation of the Recommended
+ crypto libraries; xbatmon-simple needs the Suggested X libraries.
+
+Package: chiark-really
+Section: admin
+Priority: extra
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: really - a tool for gaining privilege (simple, realistic sudo)
+ really is a program that allows certain users to become whatever user
+ they like on request.  It is a bit like sudo in that respect.
+ However, really is simpler than sudo, and doesn't give the system
+ administrator any false security promises.  So really is less of a
+ general security risk to the system.
+ .
+ Unlike sudo it does not pretend that the called account can be any
+ more secure than the calling account. so there is never a need for a
+ password.  If you wanted to restrict which commands and functions the
+ called user can perform, use userv, not really or sudo.
+ .
+ Also unlike sudo, really only works if the calling user is supposed
+ to be equivalent to root.  But, really can also be used by
+ root-equivalent users to become any user, not just root; in this way
+ it can be a replacement for certain uses of su.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..2d0cbc9
--- /dev/null
@@ -0,0 +1,79 @@
+This package contains some the utilities from Ian Jackson's private
+system `chiark.greenend.org.uk'.
+
+This package, containing the moderately portable sources and Debian
+packaging information, and the resulting Debian binary packages, was
+put together by Ian, but with assistance from other chiark users.  For
+both upstream and Debian packaging questions, please contact
+chiark-utils-maint@chiark.greenend.org.uk.
+
+
+The software included is:
+
+chiark backup, for small systems or networks.  chiark backup is
+ Copyright 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+ Copyright 1999 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+
+readbuffer/writebuffer (in the backup directory), filters for keeping
+tape drives streaming and audio output devices playing.  They are:
+ Copyright 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+
+sync-accounts, a tool for synchronising UN*X password data.  It's
+ Copyright 1999-2000,2002 Ian Jackson <ian@davenant.greenend.org.uk>
+ Copyright 2000-2001 nCipher Corporation Ltd
+
+chiark-named-conf (in the scripts directory) checks and generates
+nameserver configurations.  It is:
+ Copyright 2002 Ian Jackson <ian@chiark.greenend.org.uk>
+
+watershed, a tool for optimising runs of idempotent commands, is
+ Copyright 2007 Canonical Ltd
+
+xbatmon-simple, a simple X client for displaying ACPI battery status
+ Copyright 2004,2012 Ian Jackson <ian@chiark.greenend.org.uk>
+
+summer, a tool for reporting complete details about a filesystem tree
+ Copyright 2003-2007 Ian Jackson <ian@chiark.greenend.org.uk>
+ manpage Copyright 2006 Peter Maydell <pmaydell@chiark.greenend.org.uk>
+
+cvs-repomove and cvs-adjustroot: tools for moving CVS repositories
+ Copyright 2004-2006 Ian Jackson <ian@chiark.greenend.org.uk>
+
+cvsweb-list: cgi program to list ucgi (userv-utils) cvsweb repos
+ Copyright 2001 Ian Jackson <ian@chiark.greenend.org.uk>
+
+expire-iso8601: keeps or expires backup trees named after their dates
+ Copyright 2006 Ian Jackson <ian@chiark.greenend.org.uk>
+
+gnucap2genspic, ngspice2genspic, genspic2gnuplot:
+Tools for converting gnucap files and ngspice output files to
+genspic and genspic files to gnuplot input so they can be plotted.
+ Copyright 2004 Ian Jackson <ian@chiark.greenend.org.uk>
+
+hexterm: connect to serial port and interact in ASCII and hex (hex terminal)
+ Copyright 2005 Ian Jackson <ian@chiark.greenend.org.uk>
+palm-datebook-reminders: for mailing reminders about Palm PDA appointments
+ Copyright 2003 Ian Jackson <ian@chiark.greenend.org.uk>
+
+random-word, remountresizereiserfs, summarise-mailbox-preserving-privacy
+ Miscellaneous utilities.
+ Copyright 2004,2006 Ian Jackson <ian@chiark.greenend.org.uk>
+
+
+The chiark utilities are all 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 3 of the
+License, or (at your option) any later version.
+
+These programs are distributed in the hope that they 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/Linux system, in /usr/share/common-licenses/GPL-3, or
+with the chiark-utils source package as the file COPYING; if not,
+email me at one of the addresses above or consult the Free Software
+Foundation's website at www.fsf.org, or the GNU Project website at
+www.gnu.org.
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..62d84cf
--- /dev/null
@@ -0,0 +1,157 @@
+#!/usr/bin/make -f
+
+SHELL=/bin/bash
+
+subdirs_build_arch=    cprogs
+subdirs_nobuild=backup sync-accounts scripts
+package=       chiark-utils
+packages_indep=        chiark-backup chiark-scripts
+packages_arch= chiark-rwbuffer chiark-really chiark-utils-bin
+packages=      $(packages_indep) $(packages_arch)
+
+cwd=   $(shell pwd)
+d=     $(cwd)/debian
+t=     $d/tmp
+
+build:
+       $(checkdir)
+       set -e; for s in $(subdirs_build_arch); do $(MAKE) -C $$s all; done
+       touch build
+
+build-indep: build
+build-arch: build
+
+clean:
+       $(checkdir)
+       rm -f build
+       set -e; for s in $(subdirs_build_arch); do \
+               $(MAKE) -C $$s -i distclean || \
+               $(MAKE) -C $$s -f Makefile.in distclean; \
+       done
+       rm -rf *~ debian/tmp debian/*~ debian/files* debian/substvars*
+       rm -rf debian/sv-* debian/*.debhelper.log
+
+binary-prep:
+       $(checkdir)
+       rm -rf debian/tmp*
+       #
+       set -e; for s in $(subdirs_build_arch) $(subdirs_nobuild); do \
+               $(MAKE) -C $$s install install-docs install-examples \
+                       prefix=$t/$$s/usr \
+                       etcdir=$t/$$s/etc \
+                       varlib=$t/$$s/var/lib \
+                       mandir=$t/$$s/usr/share/man; \
+       done
+       #
+       mv $t/cprogs $t/chiark-utils-bin
+       #
+       cp -a debian/tmp/sync-accounts/* debian/tmp/scripts/.
+       rm -r debian/tmp/sync-accounts
+       mv debian/tmp/scripts debian/tmp/chiark-scripts
+       mv debian/tmp/backup debian/tmp/chiark-backup
+       #
+       set -e; for p in $(packages); do \
+               install -d $t/$$p/DEBIAN $t/$$p/usr/share/doc/$$p; \
+               cp debian/copyright debian/changelog \
+                       $t/$$p/usr/share/doc/$$p/; \
+               ln -s changelog.gz \
+                       $t/$$p/usr/share/doc/$$p/changelog.Debian.gz; \
+               gzip -9v $t/$$p/usr/share/doc/$$p/changelog; \
+               done
+       #
+       install -d $t/chiark-rwbuffer/usr/bin
+       install -d $t/chiark-rwbuffer/usr/share/man/man1
+       cd $t/chiark-utils-bin/usr/bin && \
+               mv readbuffer writebuffer $t/chiark-rwbuffer/usr/bin/
+       cd $t/chiark-utils-bin/usr/share/man/man1 && \
+               mv readbuffer.1 writebuffer.1 $t/chiark-rwbuffer/usr/share/man/man1/
+       #
+       install -d $t/chiark-backup/usr/share/man/man1
+       cp backup/man/*.1 $t/chiark-backup/usr/share/man/man1/
+       cd $t/chiark-backup/usr/share/man/man1 && \
+               for m in *.1; do \
+                       mv "$$m" backup-"$$m"; \
+               done
+       cp \
+ $t/chiark-backup/usr/share/doc/chiark-backup/examples/chiark/settings.sh \
+ $t/chiark-backup/etc/chiark-backup/settings.sh
+       #
+       install -d $t/chiark-really/usr/sbin
+       install -d $t/chiark-really/usr/share/man/man8
+       cd $t/chiark-utils-bin/usr/sbin && \
+               mv really $t/chiark-really/usr/sbin/
+       cd $t/chiark-utils-bin/usr/share/man/man8 && \
+               mv really.8 $t/chiark-really/usr/share/man/man8/
+       rm      $t/chiark-utils-bin/usr/sbin/trivsoundd \
+               $t/chiark-utils-bin/usr/share/man/man8/trivsoundd.8
+       rmdir   $t/chiark-utils-bin/usr/sbin \
+               $t/chiark-utils-bin/usr/share/man/man8
+       #
+       gzip -9f $t/*/usr/share/man/man*/*
+
+binary-hook-chiark-backup:
+binary-hook-chiark-rwbuffer:
+binary-hook-sync-accounts:
+binary-hook-chiark-scripts:
+binary-hook-chiark-really:
+binary-hook-chiark-utils-bin:
+
+binary-one:
+       set -e; for f in preinst postinst prerm postrm conffiles; do \
+               test -f debian/$p/$$f || continue; \
+               cp debian/$p/$$f $t/$p/DEBIAN/$$f; \
+               chmod u=rwX,go=rX $t/$p/DEBIAN/$$f; \
+       done
+       dh_link -p$p -Pdebian/tmp/$p
+       dpkg-gencontrol -isp -p$p -P$t/$p -Tdebian/sv-$p
+       chown -R root.root debian/tmp
+       chmod -R g-ws debian/tmp
+       debian/rules binary-hook-$p
+       dpkg --build $t/$p ..
+
+binary-indep:  checkroot build binary-prep
+       set -e; for p in $(packages_indep); do \
+               debian/rules binary-one p=$$p; done
+
+binary-arch:   checkroot build binary-prep
+       $(checkdir)
+       set -ex; for p in chiark-really chiark-utils-bin chiark-rwbuffer; do \
+               dh_strip -p$$p -Pdebian/tmp/$$p; done
+       dpkg-shlibdeps -Tdebian/sv-chiark-rwbuffer \
+               $t/chiark-rwbuffer/usr/bin/*
+       dpkg-shlibdeps -Tdebian/sv-chiark-really \
+               $t/chiark-really/usr/sbin/*
+       set -e; for f in $t/chiark-utils-bin/usr/bin/*; do \
+               case "$$f" in \
+               */xbatmon-simple)       d=Suggests      ;; \
+               */watershed|*/summer)   d=Recommends    ;; \
+               *)                      d=Depends       ;; \
+               esac; \
+               a="$$a -d$$d $$f"; \
+           done; set -x; \
+           dpkg-shlibdeps -Tdebian/sv-chiark-utils-bin $$a
+       perl -i~ -pe '                                          '\
+        -e'    next unless m/^shlibs:/;                        '\
+        -e'    s/$$/,/; s/=/=, /;                              '\
+        -e'    s/, libgmp3(?:c2)?,/, libgmp3 | libgmp3c2,/;    '\
+        -e'    s/=, /=/; s/,$$//;                              '\
+                       debian/sv-*[!~]
+       set -e; for p in $(packages_arch); \
+               do debian/rules binary-one p=$$p; done
+
+define checkdir
+       test -f cprogs/writebuffer.c
+endef
+
+# Below here is fairly generic really
+
+binary:                binary-indep binary-arch
+
+source diff:
+       @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+checkroot:
+       $(checkdir)
+       test root = "`whoami`"
+
+.PHONY: binary binary-arch binary-indep clean checkroot
diff --git a/scripts/ChiarkNNTP.pm b/scripts/ChiarkNNTP.pm
new file mode 100644 (file)
index 0000000..5b69e28
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/perl
+
+use strict qw(subs);
+use warnings;
+
+require 5.002;
+use Socket;
+use FileHandle;
+
+
+BEGIN {
+    use Exporter   ();
+    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+    # set the version for version checking
+    $VERSION     = 1.00;
+
+    @ISA         = qw(Exporter);
+    @EXPORT      = qw(cnntp_connect);
+    %EXPORT_TAGS = ( );     # eg: TAG => [ qw!name1 name2! ],
+    
+    @EXPORT_OK   = qw();
+}
+our @EXPORT_OK;
+
+sub cnntp_connect ($) {
+    my ($verbose) = @_;
+
+    my $ns=$ENV{'NNTPSERVER'};
+    if (!defined $ns or !length $ns) {
+       $ns = `cat /etc/nntpserver`;
+       chomp($ns);
+    }
+    my $port = (getservbyname("nntp", "tcp"))[2];
+    $ns = inet_aton($ns);
+    my $proto = getprotobyname("tcp");
+    my $paddr = sockaddr_in($port, $ns);
+
+    my $sock = new IO::Handle;
+    socket($sock,PF_INET,SOCK_STREAM,$proto) or die "socket: $!";
+    connect($sock,$paddr) or die "connect: $!";
+
+    $sock->autoflush(1);
+
+    return bless { S => $sock, V => $verbose };
+}
+
+sub banner_reader ($) {
+    my ($c) = @_;
+    my ($code,$l) = $c->getline();
+    $code =~ /^2\d\d/ or die "no initial greeting from server\n";
+    $c->docmd("MODE READER");
+}
+
+sub disconnect ($) {
+    my ($c) = @_;
+    close $c->{S};
+}
+
+sub putline ($$) {
+    my ($c, $line) = @_;
+    my $s = $c->{S};
+    my $v = $c->{V};
+    print $v ">>> $line\n" if $v;
+    print $s "$line\r\n";
+}
+
+sub getline_raw ($) {
+    my ($c) = @_;
+    my $s = $c->{S};
+    my $l = <$s>;
+    return $l;
+}
+
+sub getline ($) {
+    my ($c) = @_;
+    my $v = $c->{V};
+    my $l = $c->getline_raw();
+    $l =~ s/[\r\n]*$//s;
+    my $code = substr($l,0,3);
+    print $v "<<< $l\n" if $v;
+    return ($code,$l);
+}
+
+sub docmd ($$;$) {
+    my ($c,$cmd,$nocheck) = @_;
+    my ($code,$l);
+    for my $n (0,1) {
+       $c->putline($cmd);
+       ($code,$l) = $c->getline();
+       if ($code eq "480") { $c->auth(); } else { last; }
+    }
+    if (!$nocheck) {
+       $code =~ /^2\d\d/ or die "failed on `$cmd':\n$l\n";
+    }
+    return ($code,$l);
+}
+
+sub auth ($) {
+    my ($c) = @_;
+    # Authentication.
+    return if $c->{Authed}++;
+    my $auth = $ENV{"NNTPAUTH"};
+    if (defined $auth) {
+       $c->putline("AUTHINFO GENERIC $auth");
+       pipe AUTHSTDIN, TOAUTH or die "unable to create pipes";
+       pipe FROMAUTH, AUTHSTDOUT or die "unable to create pipes";
+       flush STDOUT;
+       my $pid = fork;
+       if (!defined $pid) {
+           die "unable to fork for authentication helper";
+       } elsif ($pid == 0) {
+           # we are child
+           $c->{V} = undef if $c->{V} eq 'STDOUT';
+           $ENV{"NNTP_AUTH_FDS"} = "0.1";
+           open STDIN, "<&AUTHSTDIN";
+           open STDOUT, ">&AUTHSTDOUT";
+           close $c->{S};
+           exec $auth;
+           die $!;
+       }
+       # we are parent
+       close AUTHSTDIN;
+       close AUTHSTDOUT;
+       autoflush TOAUTH 1;
+       my ($code,$l) = $c->getline(); print TOAUTH "$l\n";
+       while (<FROMAUTH>) {
+           s/[\r\n]*$//s;
+           $c->putline($_);
+           ($code,$l) = $c->getline();
+           print TOAUTH "$l\n";
+       }
+       die "failed authentication\n" unless $? == 0;
+    }
+}
+
+1;
diff --git a/scripts/Makefile b/scripts/Makefile
new file mode 100644 (file)
index 0000000..94458b6
--- /dev/null
@@ -0,0 +1,54 @@
+# Makefile
+
+# This file is part of chiark-utils, a collection of useful programs
+# used on chiark.greenend.org.uk.
+#
+# This file is:
+#  Copyright (C) 2001 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+include ../settings.make
+
+SCRIPTS=       palm-datebook-reminders random-word expire-iso8601 \
+               genspic2gnuplot gnucap2genspic ngspice2genspic \
+               cvs-repomove cvs-adjustroot remountresizereiserfs \
+               hexterm summarise-mailbox-preserving-privacy
+MANPAGES1=     palm-datebook-reminders
+
+CSCRIPTS=      named-conf
+CMANPAGES8=    named-conf
+
+all:
+
+install:
+               $(INSTALL_DIRECTORY) $(bindir)
+               set -e; for f in $(SCRIPTS); do \
+                       $(INSTALL_SCRIPT) $$f $(bindir)/$$f; done
+               set -e; for f in $(CSCRIPTS); do \
+                       $(INSTALL_SCRIPT) $$f $(bindir)/chiark-$$f; done
+
+install-docs:
+               $(INSTALL_DIRECTORY) $(man1dir) $(man8dir)
+               set -e; for f in $(MANPAGES1); do \
+                       $(INSTALL_SHARE) $$f.1 $(man1dir)/$$f.1; done
+               set -e; for f in $(CMANPAGES8); do \
+                       $(INSTALL_SHARE) $$f.8 $(man8dir)/chiark-$$f.8; done
+
+install-examples:
+
+clean:
+               rm -f *~ ./#*#
+
+distclean realclean:   clean
diff --git a/scripts/cvs-adjustroot b/scripts/cvs-adjustroot
new file mode 100755 (executable)
index 0000000..0e128cc
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+set -e
+
+usage () { echo >&2 'usage: cvs-adjustroot OLD NEW'; exit 1; }
+
+case "$#.$1" in
+4.--reinvoke)  reinvoke=true; shift    ;;
+*.-*)          usage                   ;;
+2.*)           reinvoke=false          ;;
+1.*)           usage                   ;;
+0.*)           usage                   ;;
+*)             usage                   ;;
+esac
+
+# Copyright 2004 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This script and its documentation (if any) are free software; you
+# can redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+
+old="$1"; shift
+new="$1"; shift
+
+if $reinvoke; then
+       filename="$1";
+       cmp -- "$filename" <(printf "%s\n" "$old")
+       printf "%s\n" "$new" >"$filename".new
+       mv -f -- "$filename".new "$filename"
+       exit 0
+fi
+
+find -path '*/CVS/Root' -exec cvs-adjustroot --reinvoke "$old" "$new" '{}' ';'
diff --git a/scripts/cvs-repomove b/scripts/cvs-repomove
new file mode 100755 (executable)
index 0000000..a3bb60f
--- /dev/null
@@ -0,0 +1,278 @@
+#!/bin/bash
+set -e
+print_usage () { cat <<'END'
+usage:
+  to move repository directory, cd to anywhere and
+    cvs-repomove --move local-repo module hostname remote-repo
+    cvs-repomove --move src-hostname src-repo module dst-hostname dst-repo
+  to adjust CVS/Root afterwards, in each working directory
+    cvs-repomove                automatically update **/CVS/Root
+END
+}
+
+# Copyright 2004-2006 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This script and its documentation (if any) are free software; you
+# can redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+
+# We do things in the following order:
+#                                              src             dst
+#  0. Check things                             @/..moving-to   none/..moved-to
+#  1. Rename src repo to prevent commits etc.  ..moving-to     none/..moved-to
+#  2. Make temporary copy at destination       ..moving-to     ..tmp
+#  3. Install temporary copy at destination    ..moving-to     @
+#  4. Move aside src repo                      ..moved-to      @
+
+fail () { echo >&2 "error: $1"; exit 8; }
+bad_usage () { echo >&2 "bad usage: $1"; print_usage >&2; exit 12; }
+
+move=false
+
+while [ $# -gt 0 ]; do
+       case "$1" in
+       --move)         move=true ;;
+       --help)         print_usage; exit 0 ;;
+       --)             ;;
+       -*)             bad_usage "unknown option $1" ;;
+       *)              break ;;
+       esac
+       shift
+done
+
+mn () { echo " $1"; }
+
+check_module () {
+       case "$module" in
+       *..*)           fail \
+ "moving a module with \`..' in its name is not supported" ;;
+       */*)            fail \
+ "moving a subdirectory is not supported" ;;
+       -*)             fail \
+ "moving a module whose name starts with \`-' is not supported" ;;
+       esac
+}
+
+check_hostname () {
+       case "$1" in
+       /*|.*|-*)       fail "bad hostname $dsthost" ;;
+       esac
+}
+
+check_remote_path () {
+       case "$1" in
+       *[^0-9a-zA-Z/._+,-]*) fail \
+               "pathname may not contain metacharacters, sorry" ;;
+       esac
+}
+
+do_move () {
+       check_module
+       check_hostname "$srchost"
+       check_hostname "$dsthost"
+       check_remote_path "$srcrepo/$module"
+       check_remote_path "$dstrepo/$module"
+
+       case "$dstrepo" in
+       /*)     ;;
+       *)      bad_usage "destination repo path must be absolute" ;;
+       esac
+
+       printf "moving module %s from %s:%s to %s:%s\n" \
+               "$module" "$srchost" "$srcrepo" "$dsthost" "$dstrepo"
+
+       mn "checking existing repository"
+       "$CVS_RSH" "$srchost" bash -ec "'
+ ls -d -- $srcrepo/CVSROOT >/dev/null
+       '"
+
+       dstrepotrans="$(printf '%s\n' "$dstrepo" | tr / :)"
+       movingto="moving-to-$dsthost:$dstrepotrans"
+       resume="$("$CVS_RSH" "$srchost" bash -ec "'
+ if test -d $srcrepo/$module..$movingto; then
+  echo >&2 \"    resuming previous attempt at a move\"
+  resume=true
+  if test -d $srcrepo/$module; then
+   echo >&2 \"but $srcrepo/$module exists too\"
+   exit 1
+  fi
+ else
+  resume=false
+  ls -d -- $srcrepo/$module >/dev/null
+ fi
+ set +e
+ previously=\"$(ls -d -- $srcrepo/$module..moved-to-* 2>/dev/null)\"
+ set -e
+ if [ \"x\$previously\" != x ]; then
+  echo >&2 \"    btw, module was once before moved away from here\"
+  mv -- \"\$previously\" \
+   \"\${previously/..moved-to-/..previously-\$(date +%s)-moved-to-}\"
+ fi
+ echo \$resume
+       '")"
+
+       mn "checking dst repository"
+       "$CVS_RSH" "$dsthost" bash -ec "'
+ cd $dstrepo
+ ls -d CVSROOT >/dev/null
+ if test -d $dstrepo/$module; then
+  echo >&2 module already exists in destination repo
+  exit 1
+ fi
+ for f in $module..*; do
+  case \"\$f\" in
+  *..moved-to-*)
+   echo \"    btw, module was previously at destn repo\"
+   mv -- \"\$f\" \
+    \"\${f/..moved-to-/..previously-\$(date +%s)-moved-to-}\"
+   ;;
+  *..previously-*) ;;
+  *..tmp-*)
+   echo \"    nb: possibly-stale temp/partial copy \$f\"
+   ;;
+  *..\*)
+   ;;
+  *)
+   echo >&2 \"error: found unexpected subdir \$f\"
+   exit 8
+   ;;
+  esac
+ done
+       '"
+
+       if ! $resume; then
+               "$CVS_RSH" "$srchost" bash -ec "'
+ mv -- $srcrepo/$module $srcrepo/$module..$movingto
+               '"
+       fi
+
+       mn "transferring repo data"
+       tmpid="tmp-$(uname -n).$(date +%s)"
+       "$CVS_RSH" "$srchost" bash -ec "'
+ tar -c -C $srcrepo/$module..$movingto -f - .
+       '" | "$CVS_RSH" "$dsthost" bash -ec "'
+               cd $dstrepo
+               mkdir $module..$tmpid
+               cd $module..$tmpid
+               tar --no-same-owner -xf -
+       '"
+       test "${PIPESTATUS[*]}" = "0 0"
+
+       mn "confirming move at destination repo"
+       "$CVS_RSH" "$dsthost" bash -ec "'
+               cd $dstrepo
+               mv $module..$tmpid $module
+       '"
+
+       mn "confirming move at source repo"
+       "$CVS_RSH" "$srchost" bash -ec "'
+ mv -- $srcrepo/$module..$movingto \
+  $srcrepo/$module..moved-to-$dsthost:$dstrepotrans
+       '"
+       echo "module moved successfully"
+}
+
+compute_fqdn_data () {
+       fqdn_data="$(adnshost -s - "$1" 2>/dev/null || true)"
+}
+
+do_furtle () {
+       module="$(cat CVS/Repository)"
+       oldroot="$(cat CVS/Root)"
+       goose="$oldroot"
+       check_module
+       printf "checking/updating repo for %s\n" "$module"
+       compute_fqdn_data "$(uname -n)"
+       our_fqdn_data="$fqdn_data"
+       searching=true
+       while $searching; do
+               mn "chasing geese at $goose"
+               case "$goose" in
+               *[0-9a-zA-Z]:/*)
+                       remotehost="${goose%%:*}"
+                       path="${goose#*:}"
+                       check_hostname "$remotehost"
+                       check_remote_path "$path/$module"
+                       isremote=true
+                       compute_fqdn_data "$remotehost"
+                       if [ "x$fqdn_data" = "x$our_fqdn_data" -a \
+                               "x$fqdn_data" != x ]; then
+                               isremote=false
+                               goose="$path"
+                       fi
+                       ;;
+               *:*)
+                       fail "unknown remote repository string $goose"
+                       ;;
+               /*)
+                       isremote=false
+                       path="$goose"
+                       ;;
+               *)
+                       fail "unknown repository string $goose"
+                       ;;
+               esac
+               check="
+ cd $path
+ if test -d $module; then echo good; exit 0; fi
+ if ls -d $module..moved-to-* 2>/dev/null; then exit 0; fi
+ echo bad
+"
+               if $isremote; then
+                       new_goose_info="$("$CVS_RSH" "$remotehost" \
+                                       bash -ec "'$check'")"
+               else
+                       new_goose_info="$(      bash -ec "$check")"
+               fi
+               case "$new_goose_info" in
+               good)
+                       searching=false
+                       ;;
+               bad)
+                       echo >&2 "trail went cold at $goose"
+                       exit 4
+                       ;;
+               *..moved-to-*)
+                       goose="$(printf '%s\n' \
+                               "${new_goose_info#*..moved-to-}" | \
+                               tr : / | sed -e 's,/,:,')"
+                       ;;
+               esac
+       done
+       if [ "x$goose" = "x$oldroot" ]; then
+               echo 'repo has not moved - nothing to do'
+               exit 0
+       fi
+       mn "found new repo, adjusting working tree"
+       cvs-adjustroot "$oldroot" "$goose"
+       echo 'working tree adjustment completed'
+}
+
+if $move; then
+       if [ $# = 4 ]; then
+               srchost="$(hostname -f)"; srcrepo="$1"
+               module="$2";
+               dsthost="$3"; dstrepo="$4"
+       elif [ $# = 5 ]; then
+               srchost="$1"; srcrepo="$2"
+               module="$3";
+               dsthost="$4"; dstrepo="$5"
+       else
+               bad_usage "--move needs hostname(s) and paths"
+       fi
+       do_move
+else
+       [ "$#" = 0 ] || bad_usage "without --move, give no arguments"
+       do_furtle
+fi
diff --git a/scripts/cvsweb-list b/scripts/cvsweb-list
new file mode 100755 (executable)
index 0000000..c574eb8
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+# cvsweb-list
+# This little program produces a web page listing the cvs repositories
+# available by ucgi cvsweb.  It doesn't really separate code and
+# configuration, so it's not installed by default with chiark-utils.
+
+# This file is part of chiark-utils, a collection of useful programs
+# used on chiark.greenend.org.uk.
+#
+# This file is:
+#  Copyright 2001 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+
+print <<END or die $!;
+Content-Type: text/html
+
+<head>
+<title>chiark public CVS</title>
+<link rev="made" href="mailto:webmaster\@chiark.greenend.org.uk">
+</head>
+<body>
+<h1><img src="/chiark/icon90.gif" border="0" width="128" height="64"
+alt=""> chiark users' public CVS</h1>
+<ul>
+END
+
+open UL, "/etc/userlist" or die $!;
+while (<UL>) {
+    next if m/^\#/ or !m/\S/;
+    chomp($user= $_);
+    next unless readlink "/home/$user/public-cgi/cvsweb"
+       eq '/usr/local/lib/cvsweb';
+    $hd= 0;
+    $pc= "/home/$user/public-CVS/";
+    next unless opendir D, $pc;
+    while (defined($mod= readdir D)) {
+       next unless -d "$pc/$mod";
+       next if $mod =~ m/^\./;
+       if (!$hd) {
+           print "<li><A href=\"/ucgi/~$user/cvsweb\">$user</A>" or die $!;
+           print " (<A href=\"/~$user/\">homepage</A>)" or die $!
+               if -d "/home/$user/public-html";
+           print ":" or die $!;
+           $hd= 1;
+       } else {
+           print "," or die $!;
+       }
+       print " <A href=\"/ucgi/~$user/cvsweb/$mod/\">$mod</A>" or die $!;
+    }
+    next unless $hd;
+    print "</li>\n" or die $!;
+}
+
+close UL or die $!;
+
+print <<END or die $!;
+</ul>
+<hr>
+<ADDRESS>
+  maintained by
+  <A HREF="mailto:$ENV{SERVER_ADMIN}">$ENV{SERVER_ADMIN}</A>;
+  <A href="/">chiark home page</A>
+</ADDRESS>
+</body>
+END
+
+exit 0
diff --git a/scripts/expire-iso8601 b/scripts/expire-iso8601
new file mode 100755 (executable)
index 0000000..530c48c
--- /dev/null
@@ -0,0 +1,227 @@
+#!/bin/bash
+set -e
+                       usage () {
+                       cat <<END
+usage:
+  expire-iso8601 [<options>] <number>x<interval> [<number>x<interval> ...]
+options:
+   -u<unitlen>  <interval> is measured in units of <unitlen> seconds
+                   (default is 86400, so <interval> is in days)
+   -s<slop>     allow kept items to be <slop> seconds shorter apart than
+                   specified; default is 10% of <unitlen>
+   -n           do not really delete
+   -r           recursive removal (rm -r)
+example:
+   /home/ian/junk/expire-iso8601 14x1 4x7
+      uses units of 86400s (1 day) with a slop of 8640
+      it keeps 14 daily items
+       (that is 14 items, dated no less than 86400-8640 apart)
+      and 7 weekly items
+       (that is 7 items, dated no less than 7*86400-8640 apart)
+      the 14 daily and 7 weekly items may be the same, or not
+   There is no need to sort the list of <number>x<interval> pairs.
+exit status:
+   0                   ok
+   4                   rm failed
+   8                   bad usage
+   16                  catastrophic failure
+END
+                       }
+
+# Copyright 2006 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This script and its documentation (if any) are free software; you
+# can redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+
+trap 'exit 16' 0
+badusage () { echo >&2 "bad usage: $*"; usage >&2; trap '' 0; exit 8; }
+
+#-------------------- argument parsing --------------------
+
+alldigits () {
+       [ "x${2##*[^0-9]}" = "x$2" ] || \
+               badusage "bad $1 \`$2'; must be all digits"
+       [ "$2" ] || badusage "bad $2; must be nonempty"
+       eval $1='$2'
+}
+
+rm=rm
+recurse=''
+unit=86400
+slop=''
+
+while [ $# -ge 1 ]; do
+       arg=$1; shift
+       case "$arg" in
+       --|-)   break ;;
+       --help) usage; exit 0 ;;
+       --*)    badusage "unknown option $arg" ;;
+       -*)
+               val=${arg#-?}
+               case "$arg" in
+               -n*)    rm=: ;;
+               -r*)    recurse=-r ;;
+               -u*)    alldigits unit "$val"; arg='' ;;
+               -s*)    alldigits slop "$val"; arg='' ;;
+               *)      badusage "unknown option ${1:0:2}" ;;
+               esac
+               arg=-${arg#-?}
+               if test "x$arg" != x-; then set -- "$arg" "$@"; fi
+               ;;
+       *)      set "$arg" "$@"; break ;;
+       esac
+done
+
+[ $# -ge 1 ] || badusage 'too few arguments'
+[ "$slop" ] || slop=$(( $unit / 10 ))
+
+for ni in "$@"; do
+       case "$ni" in *x*);; *) badusage "bad <number>x<interval> $ni";; esac
+       alldigits number "${ni%%x*}"
+       alldigits interval "${ni#*x}"
+done
+
+#-------------------- scanning the directory ----------
+
+# We build in $l a list of the relevant filenames and the time_t's
+# they represent.
+#
+# Each entry in $l is $time_t/$filename, and the list is
+# newline-separated for the benefit of sort(1).
+
+ls=0
+for cn in [0-9]*; do
+       case "$cn" in
+       ????-??-??)
+               conv="$cn";;
+       ????-??-??T[0-2][0-9]+[0-9][0-9][0-9][0-9]|\
+       ????-??-??T[0-2][0-9]:[0-6][0-9]+[0-9][0-9][0-9][0-9]|\
+       ????-??-??T[0-2][0-9]:[0-6][0-9]:[0-6][0-9]+[0-9][0-9][0-9][0-9])
+               conv="${cn%T*} ${cn#*T}";;
+       *)
+               echo >&2 "ignoring $cn"
+               continue;;
+       esac
+       cs=$(date -d "$conv" +%s)
+       l="$cs/$cn
+$l"
+done
+
+#-------------------- main computation --------------------
+
+# We process each minimum/extent pair, to have it select a bunch of
+# versions to keep.  We annotate entries in $l: if we are keeping
+# an entry we prepend a colon; temporarily, if we are keeping an entry
+# because of this particular minimum/extent, we prepend a comma.
+
+# For each minimum/extent pair we look at the list from most recent
+# to least recent,
+#   ie in order of increasing age
+#   ie in order of decreasing time_t
+# and each time we're more than min older than the last item we kept,
+# we mark the item to keep, until we have as many as we want.
+#
+# We build the new list (space-separated) in lnew.
+
+l=$(sort -nr <<END
+$l
+END
+)
+
+for ni in "$@"; do
+       wantcount=${ni%x*}
+
+       div=1
+
+       while true; do
+               min=$(( (${ni#*x} * $unit) / $div - $slop ))
+
+               ls=''
+               lnew=''
+               skipped=0
+               for ce in $l; do
+                       cn=${ce#*/}; cl=${ce%%/*}
+                       cs=${cl#,}; cs=${cs#:}
+                       case $cl in ,*) ls=$cs; continue;; esac
+                       if [ $wantcount != 0 ]; then
+                               if ! [ "$ls" ] || \
+                                  [ $(( $ls - $cs )) -ge $min ]; then
+                                       echo "keep (for $ni) $cn"
+                                       ce=,$ce
+                                       ls=$cs
+                                       wantcount=$(( $wantcount - 1 ))
+                               else
+                                       skipped=$(( $skipped+1 ))
+                               fi
+                       fi
+                       lnew="$lnew $ce"
+               done
+               l=$lnew
+
+               if [ $wantcount = 0 ]; then break; fi
+               printf "%s" "insufficient (for $ni) by $wantcount"
+               if [ $skipped = 0 ]; then echo; break; fi
+               div=$(( $div * 2 ))
+               echo " shortening interval ${div}x"
+       done
+
+       # s/([,:]+).*/:\1/g
+       lnew=''
+       for ce in $l; do
+               case $ce in ,*) ce=:${ce#,};; esac
+               case $ce in ::*) ce=${ce#:};; esac
+               lnew="$lnew $ce"
+       done
+       l=$lnew
+done
+
+#-------------------- execution --------------------
+
+trap '' 0
+exitstatus=0
+
+nonbroken_echo () { (echo "$@"); }
+# While we have subprocesses, we have to avoid bash calling write(1,...)
+# because of a bug in bash (Debian #382798), so we arrange for a subshell
+# for each echo.
+
+jobs=''
+for ce in $l; do
+       case $ce in
+       :*);;
+       *)
+               cn=${ce#*/}
+               nonbroken_echo "expire $cn"
+               $rm $recurse -- $cn &
+               jobs="$jobs $!"
+               ;;
+       esac
+done
+
+if [ "$jobs" ]; then
+       nonbroken_echo "all running"
+fi
+
+for job in $jobs; do
+       wait $job || exitstatus=4
+done
+
+if [ $exitstatus = 0 ]; then
+       echo "complete"
+else
+       echo "complete, but problems deleting"
+fi
+
+exit $exitstatus
diff --git a/scripts/genspic2gnuplot b/scripts/genspic2gnuplot
new file mode 100755 (executable)
index 0000000..7c213c3
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+#   genspic2gnuplot - Copyright 2004 Ian Jackson - see below
+#
+# Reads a `genspic' file on stdin.  Takes exactly one arg, <pfx>.
+#
+# Produces various output files:
+#  <pfx>.gnuplots.sh                     run this to display results
+#  <pfx>,<Kind><N>.gnuplot-cmd           gnuplot script for displaying:
+#  <pfx>,<Kind><N>-<M>.gnuplot-data      gnuplot-format input data
+#  <pfx>,gnuplot-fifo                    working fifo for .gnuplots.sh
+# where
+#  <Kind> is Freq or Time (according to the type of analysis)
+#  <N>    is the count, starting at 0, of which report this is from gnucap
+#  <M>    is the individual column of Y data
+#
+# Limitations
+#
+#  There's no easy way to mess with the gnuplot settings.
+#
+#  This whole scheme is very clumsy.  There should be a driver program
+#  with command-line option parsing.
+
+# This program and its documentation 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 3, or (at your option) any later version.
+# 
+# This program and its documentation are distributed in the hope that
+# they 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+die unless @ARGV==1;
+die if $ARGV[0] =~ m/^\-/;
+
+($ofb)= @ARGV;
+$sof= "$ofb.gnuplots.sh";
+open A, "> $sof" or die $!;
+system 'chmod','+x',"$sof"; $? and die $?;
+print A <<END
+#!/bin/sh
+set -e
+fi=$ofb,gnuplot-fifo
+rm -f \$fi
+mkfifo -m 600 \$fi
+END
+    or die $!;
+
+for (;;) {
+    ($c,@s)= split /\s+/, <STDIN>;
+    if ($c eq 'S') {
+       ($cplot,$logxy,@columns) = @s;
+       $cplot .= $counter{$cplot}++;
+       unshift @columns, 'x:';
+       @mmm= map { s/^(\w+)\:// or die; $1; } @columns;
+       open S, "> $ofb,$cplot.gnuplot-cmd" or die $!;
+       print S <<END
+set data style lines
+set title '$cplot'
+END
+        or die $!;
+       print S "set logscale xy\n" or die $! if $logxy;
+       print S "set y2tics autofreq\n" or die $! if grep { $_ eq 'y2' } @mmm;
+       undef %min;
+       undef %max;
+       for ($yn=1; $yn<=$#columns; $yn++) {
+           open "O$yn", "> $ofb,$cplot-$yn.gnuplot-data" or die $!;
+       }
+    } elsif ($c eq 'T') {
+       die unless @mmm;
+       die if @s;
+       foreach $mmm (keys %min) {
+           print S "set ${mmm}range [$min{$mmm}:$max{$mmm}]\n" or die $!;
+       }
+       $sep= "plot ";
+       for ($yn=1; $yn<=$#columns; $yn++) {
+           close "O$yn" or die $!;
+           $mmm[$yn] =~ m/^y2?$/ or die "$mmm[$yn]";
+           $axes= $mmm[$yn]; $axes =~ s/^y$/y1/;
+           $yoff= 1-$yn;
+           print S "$sep\\\n".
+               " '$ofb,$cplot-$yn.gnuplot-data'".
+                   " axes x1$axes title '$columns[$yn]'"
+                       or die $!;
+           $sep= ',';
+       }
+       print S "\n\npause -1\n" or die $!;
+       close S or die $!;
+       print A "  gnuplot $ofb,$cplot.gnuplot-cmd <\$fi &\n" or die $!;
+       @mmm=@columns=();
+    } elsif ($c eq 'D') {
+       die unless @mmm;
+       @numbers= @s;
+       die unless @numbers == @columns;
+       for ($yn=0; $yn<=$#columns; $yn++) {
+           $_= $numbers[$yn];
+           $mmm= $mmm[$yn];
+           $min{$mmm}= $_ unless exists($min{$mmm}) && $min{$mmm} <= $_;
+           $max{$mmm}= $_ unless exists($max{$mmm}) && $max{$mmm} >= $_;
+           if ($yn) {
+               printf {"O$yn"} "%s %s\n", $numbers[0], $_
+                   or die $!;
+           }
+       }
+    } elsif ($c eq 'F') {
+       last;
+    } else {
+       die;
+    }
+}
+
+print A <<END
+exec 3>\$fi
+printf 'hit return to quit: '
+read
+exec 3>&-
+END
+    or die $!;
+close A or die $!;
+
+print ": generated ; $sof\n" or die $!;
+
+# $Id: genspic2gnuplot,v 1.6 2007-09-21 21:21:15 ianmdlvl Exp $
diff --git a/scripts/gnucap2genspic b/scripts/gnucap2genspic
new file mode 100755 (executable)
index 0000000..dc150a1
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/perl
+#   gnucap2genspic - Copyright 2004 Ian Jackson - see below
+#
+# Reads the output from gnucap and outputs a `genspic' file
+# for use with genspic2gnuplot.  Takes no arguments or options.
+#
+# Limitations
+#
+#  Only Freq (.AC) and Time (.TRAN) plots have been tested.  If
+#   other types go wrong they can probably be fixed by adding code for
+#   them to startplot().
+#
+#  Displaying voltages and currents on the same .TRAN graph won't work
+#   well because they currently have to have the same Y scale.  This
+#   could be fixed by assigning carefully to $mmm in startplot().
+#
+#  This whole scheme is very clumsy.  There should be a driver program
+#  with command-line option parsing.
+
+# This program and its documentation 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 3, or (at your option) any later version.
+# 
+# This program and its documentation are distributed in the hope that
+# they 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+die if @ARGV;
+
+%facttimes= qw(f 1e-15
+              p 1e-12
+              n 1e-9
+              u 1e-6
+              m 1e-3
+              K 1e3
+              Meg 1e6
+              G 1e9
+              T 1e12);
+
+sub startplot () {
+    $logxy= 0;
+    for ($yn=1; $yn<=$#columns; $yn++) {
+       $mmm[$yn]= 'y';
+    }
+    if ($kind eq 'Freq') {
+       $logxy= 1;
+       for ($yn=1; $yn<=$#columns; $yn++) {
+           die unless $columns[$yn] =~ m/.*([MP])\(\d+\)$/i;
+           $mmm[$yn]= 'y2' if uc $1 eq 'P';
+       }
+    }
+    printf "S %s %d", $cplot, $logxy or die $!;
+    for ($yn=1; $yn<=$#columns; $yn++) {
+       printf " %s:%s", $mmm[$yn], $columns[$yn] or die $!;
+    }
+    print "\n" or die $!;
+}
+sub endplot () {
+    return unless defined $kind;
+    print "T\n" or die $!;
+}
+
+$readahead= <STDIN>;
+for (;;) {
+    $linesofar= $readahead;
+    for (;;) {
+       $readahead= <STDIN>;
+       last unless $readahead =~ s/^\+//;
+       die unless length $linesofar;
+       $linesofar =~ s/\n$//;
+       $linesofar .= $readahead;
+    }
+    $_= $linesofar;
+    last unless length;
+    s/\s+$//;
+    
+    if (m/^\#(\w+)/) {
+       endplot();
+       $kind= $1;
+       @columns= split /\s+/;
+       $cplot= $kind;
+       startplot();
+       next;
+    } elsif (!defined $kind) {
+       next;
+    } elsif (s/^\s+//) {
+       @numbers= split /\s+/;
+       map {
+           if (m/^(\-?\d+\.\d*)([A-Za-z]+)$/) {
+               die "factor $2" unless exists $facttimes{$2};
+               $_= $1*$facttimes{$2};
+           }
+       } @numbers;
+       print "D @numbers\n" or die $!;
+    } else {
+       die "$_ ?";
+    }
+}
+die "no plots" unless defined $kind;
+endplot();
+print "F\n" or die $!;
+
+# $Id: gnucap2genspic,v 1.4 2007-09-21 21:21:15 ianmdlvl Exp $
diff --git a/scripts/hexterm b/scripts/hexterm
new file mode 100755 (executable)
index 0000000..9a1b047
--- /dev/null
@@ -0,0 +1,311 @@
+#!/usr/bin/tclsh8.4
+set comment {
+#
+Use of the screen:
+0         1         2         3         4         5         6         7
+xxxE hh hh hh hh  hh hh hh hh  hh hh hh hh  hh hh hh hh_| abcd e_.. .... ...._|
+}
+# Display:
+#      | is a vertical delimiter
+#       E is either | to mean echo is on or ' to mean it is off
+#      hh are hex digits of output:
+#              00-ff   actual hex data (bold for stuff we entered)
+#              0-f     under cursor: one digit entered, need the next
+#      abcde_.... are ASCII output:
+#              .       things we can't print including SPC and _
+#      in both, we may see
+#                      space we haven't yet filled
+#              _       cursor when in other tab
+#       xxx     number of bytes read/written so far
+# Keystrokes:
+#      TAB     switch between hex and literal mode
+#      ^C, ^D  quit
+#      ^Z      suspend
+# Keystrokes in hex mode only:
+#      RET     move to a new line; if already at start of line,
+#                set count to 0
+#      DEL     clear any entered hex digit
+#      SPC     send 00
+#      '       toggle echo
+# nyi:
+#      G-Z     record last bytes we transmitted and store in memory
+#               if we were halfway through a hex byte, first digit
+#               is length of string to record
+#      g-z     play back memory
+
+
+# Copyright 2005 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This script and its documentation (if any) are free software; you
+# can redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+
+if {[llength $argv] != 1} { error "need serial port arg" }
+
+set port [lindex $argv 0]
+
+set count 0
+set lit 0 ;# 1 means literal (ASCII) entry mode
+set echo 1
+
+proc p {s} {
+    puts -nonewline $s
+}
+
+proc tput {args} {
+    global tput
+    if {[catch { set s $tput($args) }]} {
+       set s [eval exec tput $args]
+       set tput($args) $s
+    }
+    p $s
+}
+
+proc csr_pos {lit bytenum} {
+    set x [expr {
+       (!$lit ? (3*$bytenum) : 53+$bytenum)
+       + ($bytenum>>2) - (2-$lit)*($bytenum==16)
+       + 5
+    }]
+    tput hpa $x
+}
+
+proc csr_this {} { global lit x; csr_pos $lit $x }
+proc csr_other {} { global lit x; csr_pos [expr {!$lit}] $x }
+proc csrs_erase {} { csr_this; p " "; csr_other; p " " }
+proc csr_this_show {} {
+    global h1
+    csr_this; if {[info exists h1]} { p $h1; p "\b" }
+}
+proc csrs_show {} {
+    csr_other; p _
+    csr_this_show
+}
+
+proc echop {} {
+    global echo
+    return [expr {$echo ? "|" : "'"}]
+}
+
+proc newline {} {
+    global x echo count
+    if {[info exists x]} { csrs_erase; p "\r\n" }
+    set x 0
+    p [format "%3x%s%*s|%*s|" $count [echop] 52 "" 21 ""]
+    csrs_show
+}
+
+proc p_ch_spaces {} {
+    global x lit
+    if {$x==15} return
+    if {$lit} { p " " }
+    if {($x & 3) != 3} return
+    p " "
+}
+
+proc p_rmso {smso} {
+    if {[string length $smso]} { tput sgr0 }
+}
+
+proc ch {d smso} {
+    global lit x count
+    if {$x == 16} newline
+    if {[string length $smso]} { tput $smso }
+    set h [format %02x [expr {$d & 0xff}]]
+    set c [format %c [expr {($d > 33 && $d < 127 && $d != 95) ? $d : 46}]]
+    if {$lit} {
+       p $c; csr_other; p $h
+       p_ch_spaces
+       p_rmso $smso
+       p _
+    } else {
+       p $h; csr_other; p $c
+       p_ch_spaces
+       p_rmso $smso
+       p _
+    }
+    incr x
+    set count [expr {($count+1) & 0xfff}]
+    csr_this_show
+}
+
+proc onreadp {} {
+    global p
+    while 1 {
+       set c [read $p 1]
+       binary scan $c c* d
+       if {![llength $d]} {
+           if {[eof $p]} { error "eof on device" }
+           return
+       }
+       ch $d {}
+    }
+}
+
+proc transmit {d} {
+    global p echo
+    puts -nonewline $p [format %c $d]
+    if {$echo} { ch $d bold }
+}
+
+proc k_echo {} {
+    global echo
+    set echo [expr {!$echo}]
+    tput hpa 3
+    p [echop]
+    csr_this
+}
+
+proc k_newline {} {
+    global count x
+    if {$x} {
+       newline
+    } else {
+       set count 0
+       p "\r"
+       p [format %3x $count]
+       csr_this
+    }
+}
+
+proc k_switch {} {
+    global lit h1
+    csrs_erase
+    catch { unset h1 }
+    set lit [expr {!$lit}]
+    csrs_show
+}
+
+proc k_stop {} {
+    restore
+    exit 0
+}
+
+proc k_suspend {} {
+    restore
+    exec kill -TSTP [info pid]
+    setup
+}
+
+proc k_noparthex {} {
+    global h1
+    csrs_erase
+    catch { unset h1 }
+    csrs_show
+}
+
+proc k_hexdigit {c} {
+    global h1 echo
+    if {![info exists h1]} { set h1 $c; p $c; p "\b"; return }
+    set d [expr 0x${h1}${c}]
+    unset h1
+    transmit $d
+    if {!$echo} { p " \b" }
+}
+
+proc onreadk {} {
+    global lit
+    while 1 {
+       set c [read stdin 1]
+       binary scan $c c* d
+       if {![llength $d]} {
+           if {[eof stdin]} { error "eof on stdin" }
+           return
+       }
+       switch -exact $d {
+           9 { k_switch; continue }
+           3 - 4 { k_stop; continue }
+           26 { k_suspend; continue }
+       }
+       if {$lit} { transmit $d; continue }
+       switch -exact $d {
+           13 { k_newline; continue }
+           32 { transmit 0; continue }
+           39 { k_echo; continue }
+           127 { k_noparthex; continue }
+       }
+       if {$d >= 48 && $d <= 57} { k_hexdigit $c; continue }
+       set kl [expr {$d | 32}]
+       if {$d >= 97 && $d <= 102} { k_hexdigit $c; continue }
+       p "\a"
+    }
+}
+
+proc try {script} {
+    if {[catch { uplevel 1 $script } emsg]} {
+       catch { puts stderr "(warning: $emsg)" }
+    }
+}
+
+proc tryv {variable script} {
+    upvar #0 $variable var
+    if {![info exists var]} return
+    uplevel 1 "
+        global $variable
+        $script
+    "
+    unset var
+}
+
+proc restore {} {
+    tryv x { puts "\r\n" }
+    try { fconfigure stdin -blocking true }
+    try { fconfigure stdout -blocking true }
+    tryv term_stty { exec stty $term_stty }
+    tryv p { close $p }
+}
+
+proc setup {} {
+    global term_stty port p
+
+    set term_stty [exec stty -g]
+
+    set p [open $port {RDWR NONBLOCK} 0]
+    
+    exec stty          min 1 time 0 -istrip -ocrnl -onlcr -onocr -opost \
+                      -ctlecho -echo -echoe -echok -echonl -iexten -isig \
+                      -icanon -icrnl
+    exec stty -F $port min 1 time 0 -istrip -ocrnl -onlcr -onocr -opost \
+                      -ctlecho -echo -echoe -echok -echonl -iexten -isig \
+                      -icanon -icrnl \
+           9600 clocal cread -crtscts -hup -parenb cs8 -cstopb \
+           -ixoff bs0 cr0 ff0 nl0 -ofill -olcuc
+
+    fconfigure $p -blocking false -buffering none -encoding binary \
+           -translation binary
+
+    fconfigure stdin -blocking false -buffering none -translation binary
+    fconfigure stdout -blocking false -buffering none -translation binary
+
+    newline
+
+    fileevent stdin readable onreadk
+    fileevent $p readable onreadp
+}
+
+proc bgerror {m} {
+    try {
+       restore
+       global errorInfo errorCode
+       puts stderr "$m\n$errorCode\n$errorInfo"
+    }
+    exit 127
+}
+
+if {[catch setup emsg]} {
+    restore
+    error $emsg $errorInfo $errorCode
+}
+
+vwait quit
diff --git a/scripts/named-conf b/scripts/named-conf
new file mode 100755 (executable)
index 0000000..120f2b7
--- /dev/null
@@ -0,0 +1,1175 @@
+#!/usr/bin/perl -w
+# This is chiark-named-conf, which is Copyright 2002 Ian Jackson.
+#
+# chiark-named-conf and its manpage are free software; you can
+# redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+use strict;
+use IO::File;
+use Data::Dumper;
+use POSIX;
+use Fcntl qw(:DEFAULT :flock);
+
+# bastard Perl wants me to do this now !
+sub loarg();
+sub soarg();
+sub usageerr ($);
+sub cfg_fail ($);
+sub read_config ($);
+sub qualify ($);
+sub bad_modifiers ($);
+sub zone_conf ($$$$$@);
+sub set_output($);
+sub progress ($$);
+sub verbose ($);
+sub process_zones (@);
+sub zone_warning ($$);
+sub zone_warnmore ($);
+sub zone_check_full ();
+sub zone_reset();
+sub zone_investigate();
+sub zone_check_nsrrset ($$$$);
+sub zone_ns_name ($$);
+sub zone_server_queue ($$$$$);
+sub zone_server_addr ($$$$$);
+sub zone_check_soa ($$$$);
+sub zone_consistency();
+sub zone_servers_ok ();
+sub zone_consistency_set ($%);
+sub zone_check_local ();
+sub zone_servers_simplefind ();
+sub zone_server_simple ($$$);
+sub zone_style ($$);
+sub mail_zone_before ();
+sub mail_zone_after ();
+sub pmail ($);
+sub ptime ($);
+sub mail_zone_mail ();
+sub zone_output ();
+sub output_files ();
+sub debug_dump ($);
+sub debug_trace ($);
+sub has_suffix_of ($$);
+sub lookup ($$$$);
+sub dig (&$$$);
+sub domain_canon ($$);
+
+use vars qw($quis $stdout_fh $stderr_fh
+           $mode $doall $domail
+           $etcfile $where
+           $debug $needglue $localonly $repeat $verbosity
+           $admin $mail_state_dir $mail_max_warnfreq
+           $progress_fh $warn_fh $modifiers
+           %group2modcmd %group2used);
+
+$|=1;
+$quis= $0; $quis =~ s,.*/,,;
+
+$mode= '';
+$doall= 0;
+$etcfile= "/etc/bind/chiark-conf-gen.zones";
+$where= '<built-in>';
+$debug= 0;
+$needglue= 1;
+$localonly= 0;
+$verbosity= 2;
+$admin=''; $mail_state_dir=''; $mail_max_warnfreq= 50;
+$repeat= 0;
+$domail= '';
+$modifiers= '';
+$group2modcmd{'foreign'}= '$!*@?';
+$group2used{'foreign'}= 1;
+
+($progress_fh= $stdout_fh= new_from_fd IO::Handle(1,'w') and
+ $warn_fh= $stderr_fh = new_from_fd IO::Handle(2,'w'))
+    or die "$quis: setup standard filehandles: $!\n";
+    
+use vars qw($dig_owner $dig_type $dig_rdata);
+
+while (@ARGV && $ARGV[0] =~ m/^\-/) {
+    $_= shift @ARGV;
+    if (s/^\-\-//) {
+       last if m/^$/;
+       if (m/^(yes|no|force)$/) { m/^./; $mode= $&; $domail=''; }
+       elsif (m/^nothing$/) { $mode= 'x'; $domail=''; }
+       elsif (m/^mail\-(first|middle|final|final\-test)$/) {
+           $mode='n';
+           $domail=$1;
+       }
+       elsif (m/^all$/) { $doall=1; }
+       elsif (m/^config$/) { $etcfile= loarg(); $where= '--config option'; }
+       elsif (m/^glueless$/) { $needglue=0; }
+       elsif (m/^localonly$/) { $localonly=1; }
+       elsif (m/^quiet$/) { $verbosity--; }
+       elsif (m/^repeat$/) { $repeat=1; }
+       elsif (m/^verbose$/) { $verbosity++; }
+       else { usageerr("unknown option --$_"); }
+    } else {
+       s/^\-//;
+       last if m/^$/;
+       while (m/^./) {
+           if (s/^[ynf]//) { $mode=$&; $domail=''; }
+           elsif (s/^A//) { $doall=1; }
+           elsif (s/^C//) { $etcfile= soarg(); $where= '-C option'; }
+           elsif (s/^D//) { $debug++; }
+           elsif (s/^g//) { $needglue=0; }
+           elsif (s/^l//) { $localonly=1; }
+           elsif (s/^m(\w+)(\W+)$//) {
+               my ($g,$m) = ($1,$2);
+               $group2modcmd{$g}=$m;
+               usageerr("modifiers $m for group $g: $@") if bad_modifiers($m);
+           }
+           elsif (s/^q//) { $verbosity--; }
+           elsif (s/^r//) { $repeat=1; }
+           elsif (s/^v//) { $verbosity++; }
+           else { usageerr("unknown option -$&"); }
+       }
+    }
+}
+
+sub loarg() { usageerr("missing option value") if !@ARGV; return shift @ARGV; }
+sub soarg() { my ($rv); $rv=$_; $_=''; return length $rv ? $rv : loarg(); }
+
+usageerr("-q may be specified at most twice") if $verbosity<0;
+usageerr("-v may be specified at most once") if $verbosity>3;
+usageerr("-D may be specified at most twice") if $debug>2;
+usageerr("must specify either -f|-y|-n or zones (and not both)")
+    if !!$mode == !!@ARGV && !$domail;
+
+sub usageerr ($) {
+    die <<END;
+$_[0]
+
+usage: chiark-named-conf [options] -f|-y|-n|<zone>...
+operation modes:
+ -f --force   install without checking
+ -y --yes     check and install
+ -n --no      check only (configured zones)
+ --nothing    list zones only
+ --mail-*     send mail about broken zones (see manpage)
+ <zone> ...   check only (specified zones, even unconfigured ones)
+additional options:
+ -A --all       report on zones marked ? (ones we know are broken)
+ -D             debug $quis (does not help debug your DNS config)
+ -g --glueless  do not warn about any glueless referrals (not recommended)
+ -l --localonly full checks only on zones which we primary
+ -q --quiet     no output for OK zones
+ -r --repeat    repeat warnings for all sources of imperfect data
+ -v --verbose   extra verbose info about each zone
+ -C|--config <DIR/FILE  use FILE as default config and DIR as default dir
+
+chiark-named-conf is Copyright 2002 Ian Jackson.  It is Free software, under
+the GNU General Public License, and you are welcome to change it and/or
+distribute copies under certain conditions.  There is ABSOLUTELY NO WARRANTY.
+END
+}
+
+cfg_fail("config filename $etcfile should not be directory")
+    if $etcfile =~ m,/$,;
+
+use vars qw($default_dir);
+$default_dir= $etcfile =~ m,^.*/, ? $& : './';
+
+use vars qw($slave_dir $slave_prefix $slave_suffix);
+$slave_dir= 'slave';
+$slave_prefix= '';
+$slave_suffix= '';
+
+use vars qw(@self_ns @self_soa @self_addr @forbid_addr @forbid_slave
+           @conv_glueless @indirect_glue);
+@self_ns= @self_soa= @self_addr= @forbid_addr= @forbid_slave= @indirect_glue= ();
+@conv_glueless= qw(in-addr.arpa ip6.arpa ip6.int);
+
+use vars qw(%zone_cfg @zone_cfg_list);
+%zone_cfg= ();
+@zone_cfg_list= ();
+
+use vars qw($output $default_output %output_contents);
+$output= '';
+$default_output= '';
+%output_contents= ();
+
+use vars qw($check $install);
+$check= $mode !~ m/^[fx]/;
+$install= $mode =~ m/^[yf]/;
+
+read_config($etcfile);
+debug_dump('@zone_cfg_list %zone_cfg');
+process_zones(!@ARGV ? @zone_cfg_list : @ARGV);
+debug_dump('%output_contents');
+output_files() if $install;
+
+$stdout_fh->close or die "$quis: write messages to stdout: $!\n";
+$stderr_fh->close or die "$quis: write messages to stderr: $!\n";
+exit 0;
+
+#-------------------- configuration reading
+
+sub cfg_fail ($) { die "$quis: $where:\n $_[0]\n"; }
+
+sub read_config ($) {
+    my ($if) = @_;
+    my ($fh,$z,@self,$before,$group,
+       $mod,$dir,$prefix,$suffix,$subfile,$lprefix,$lsuffix,$zf);
+    local ($_);
+
+    $fh= new IO::File $if,'r';
+    unless ($fh) {
+       return if $! == &ENOENT;
+       cfg_fail("open $if:\n $!");
+    }
+    $before= '';
+    for (;;) {
+       if (!defined($_= <$fh>)) {
+           cfg_fail("read config file $if:\n $!") if $fh->error();
+           last;
+       }
+       chomp; s/\s+$//;
+       if (s/\\$//) { $before.= $_; next; }
+       $_= $before.$_;
+       $before= '';
+       s/^\s+//;
+       $where= "$if:$.";
+       next if m/^\#/;
+       last if m/^end$/;
+       next unless m/\S/;
+       if (m/^self(\-ns|\-soa|)\s+(\S.*\S)/) {
+           @self= split /\s+/, $2;
+           @self_ns= @self if $1 ne '-soa';
+           @self_soa= @self if $1 ne '-ns';
+       } elsif (m/^serverless\-glueless\s+(\S.*\S)/) {
+           @conv_glueless= split /\s+/, $1;
+       } elsif (m/^allow\-indirect\-glue\s+(\S.*\S)/) {
+           @indirect_glue= split /\s+/, $1;
+       } elsif (m/^self\-addr\s+([0-9. \t]+)/) {
+           @self_addr= split /\s+/, $1;
+       } elsif (m/^forbid\-addr(?:\s+([0-9. \t]+))?/) {
+           @forbid_addr= defined $1 ? split /\s+/, $1 : ();
+       } elsif (m/^forbid\-slave(?:\s+([0-9. \t]+))?/) {
+           @forbid_slave= defined $1 ? split /\s+/, $1 : ();
+       } elsif (m,^
+                primary\-dir (\W*)
+                \s+ (\S+)/([^/ \t]*)
+                (?: \s+ ([^/ \t]*) (?: (/.+) )?
+                 )?
+                $,x) {
+           ($mod, $dir, $prefix, $suffix, $subfile) =
+               ($1,qualify($2),$3,$4,$5);
+           cfg_fail("modifiers $mod for directory $dir: $@")
+               if bad_modifiers($mod);
+           $suffix= '' if !defined $suffix;
+           $subfile= '' if !defined $subfile;
+           $suffix= '_db' if !length $suffix && !length $subfile;
+           if (-d "$dir/$prefix") { $dir.='/'; $dir.=$prefix; $prefix=''; }
+           opendir D, $dir or cfg_fail("open primary-dir $dir:\n $!");
+           $lprefix= length $prefix; $lsuffix= length $suffix;
+           while (defined($_= readdir D)) {
+               next if m/^\./ && !$lprefix;
+               next unless length > $lprefix+$lsuffix;
+               next unless substr($_,0,$lprefix) eq $prefix;
+               next unless substr($_,length($_)-$lsuffix) eq $suffix;
+               $z= substr($_,$lprefix,length($_)-($lprefix+$lsuffix));
+               $zf= $dir.'/'.$prefix.$z.$suffix.$subfile;
+               if (!stat $zf) {
+                   next if length $subfile && $! == &ENOENT;
+                   cfg_fail("cannot stat zonefile $zf:\n $!");
+               }
+               -f _ or cfg_fail("zonefile $zf is not a plain file");
+               zone_conf($z,'primary','p',$mod,$zf);
+           }
+           closedir D or cfg_fail("close primary-dir $dir:\n $!");
+       } elsif (m/^primary(\W*)\s+(\S+)\s+(\S+)$/) {
+           zone_conf($2,'primary','p',$1,qualify($3));
+       } elsif (m/^published(\W*)\s+(\S+)\s+([0-9.\t]+)$/) {
+           zone_conf($2,'published','s',$1,'',$3);
+       } elsif (m/^stealth(\W*)\s+(\S+)\s+([0-9. \t]+)$/) {
+           zone_conf($2,'stealth','u',$1,'',split /\s+/, $3);
+       } elsif (m/^modifiers\s+(\W+)(?:\s+(\w+))$/) {
+           ($mod,$group) = ($1,$2);
+           cfg_fail("modifiers $mod for group $group: $@")
+               if bad_modifiers($mod);
+           if (exists $group2modcmd{$group}) {
+               $mod= $group2modcmd{$group};
+               $group2used{$group}++;
+           }
+           $modifiers= $mod;
+       } elsif (m/^slave\-dir\s+(\S+)(?:(?:\s+(\S+))\s+(\S+))?$/) {
+           ($slave_dir, $slave_prefix, $slave_suffix) = (qualify($1),$2,$3);
+           $slave_prefix='' if !defined $slave_prefix;
+           $slave_suffix='' if !defined $slave_suffix;
+       } elsif (m/^output\s+bind8\+(\S+)$/) {
+           cfg_fail("default output may not apply to only some zones")
+               if @zone_cfg_list && length $default_output;
+           set_output(qualify($1));
+       } elsif (m/^include\s+(\S+)$/) {
+           read_config($1);
+       } elsif (m/^admin\s+(\S+)$/) {
+           $admin=$1;
+       } elsif (m/^mail\-state\-dir\s+(\S+)$/) {
+           $mail_state_dir= $1;
+       } elsif (m/^mail\-max\-warnfreq\s+(\d{1,3}(?:\.\d{0,5})?)$/) {
+           cfg_fail("mail-max-warnfreq must be <=100") if $1>100;
+           $mail_max_warnfreq= $1;
+       } else {
+           cfg_fail("unknown configuration directive".
+                    " or incorrect syntax or arguments:\n".
+                    " \`$_'");
+       }
+    }
+    foreach $group (keys %group2modcmd) {
+       next if exists $group2used{$group};
+       cfg_fail("command line specifies modifier group $group".
+                " but missing in configuration file");
+    }
+    $fh->close or cfg_fail("close config file $if:\n $!");
+}
+
+sub qualify ($) {
+    my ($i) = @_;
+    $i= "$default_dir$i" unless $i =~ m,^/,;
+    return $i;
+}
+
+sub bad_modifiers ($) {
+    local ($_) = @_;
+    if (!eval {
+       die "bad modifier $&" if m/[^!*\$\@~?]/;
+       die "repeated modifier $1" if m/(.).*\1/;
+       1;
+    }) {
+       $@ =~ s/\n//;
+       return 1;
+    }
+    return 0;
+}
+
+sub zone_conf_settings ($$) {
+    my ($cfg,$zone) = @_;
+    my ($sfx,$aref);
+    foreach $sfx (qw(self_soa self_ns self_addr forbid_addr forbid_slave
+                    conv_glueless indirect_glue)) {
+       { no strict 'refs'; $aref= [ @$sfx ]; }
+       @$aref or cfg_fail("failed to specify $sfx before zone")
+           if $sfx =~ m/^self/;
+       $cfg->{$sfx}= $aref;
+    }
+    foreach $sfx (qw(self_soa self_ns)) {
+       map { s/\*$/$zone/ } @{ $zone_cfg{$zone}{$sfx} };
+    }
+}
+
+sub zone_conf ($$$$$@) {
+    my ($zone,$style,$sabbr,$mod,$file,@servers) = @_;
+    $file= qualify("$slave_dir/$slave_prefix".$zone.$slave_suffix)
+       unless length $file;
+    if (!length $output) {
+       $default_output= qualify('chiark-conf-gen.bind8')
+           unless length $default_output;
+       set_output($default_output);
+    }
+    cfg_fail("redefined zone $zone\n".
+            " earlier definition $zone_cfg{$zone}{'where'}")
+       if exists $zone_cfg{$zone};
+    $zone_cfg{$zone}{'where'}= $where;
+    $zone_cfg{$zone}{'file'}= $file;
+    $zone_cfg{$zone}{'style_p'}= $style.$mod;
+    $zone_cfg{$zone}{'s'}= "$sabbr $mod $modifiers";
+    # p)rimary s)econdary u)npub f)oreign
+    # followed by modifiers, first per-zone, then default
+    $zone_cfg{$zone}{'servers'}= [ @servers ];
+    if ($domail) {
+       length $admin && length $mail_state_dir or
+           cfg_fail("mailing but failed to specify admin".
+                    " or mail-state-dir before zone");
+       $zone_cfg{$zone}{'admin'}= $admin;
+       $zone_cfg{$zone}{'maildir'}= qualify($mail_state_dir);
+       $zone_cfg{$zone}{'mailmwarn'}= $mail_max_warnfreq;
+    }
+    zone_conf_settings($zone_cfg{$zone}, $zone);
+    $zone_cfg{$zone}{'output'}= $output;
+    push @zone_cfg_list, $zone;
+}
+
+sub set_output($) {
+    my ($newout) = @_;
+    $output= $newout;
+    $output_contents{$output}= '';
+}
+
+#-------------------- checking
+
+use vars qw($zone $cfg $warnings %zone_warnings);
+$warnings= 0;
+
+sub progress ($$) {
+    my ($minv,$m) = @_;
+    return if $verbosity < $minv;
+    print $progress_fh "$m\n" or die "$quis: $zone: write log: $!\n";
+}
+
+sub verbose ($) { progress(3, '    ' . $_[0]); }
+
+sub process_zones (@) {
+    my (@zones) = @_;
+    local ($zone,$cfg);
+
+    foreach $zone (@zones) {
+       if ($zone =~ m/\.$/) {
+           zone_warning("zone specified with trailing dot -".
+                        " will not work", '');
+       }
+
+       $cfg= $zone_cfg{$zone};
+       if (!$cfg) {
+           $cfg= {
+               'style_p' => 'foreign',
+               's' => "f $group2modcmd{'foreign'}",
+               'servers' => [ ],
+           };
+           zone_conf_settings($cfg, $zone);
+       }
+
+       mail_zone_before() or next
+           if $domail;
+       zone_reset();
+       progress(1, sprintf "%-20s %s", $zone, $$cfg{'style_p'});
+       if ($check && ($doall || !zone_style('?',0))) {
+           eval {
+               if ($localonly && $cfg->{'s'} =~ m/f/) {
+                   zone_warning("foreign zone specified with -l",'');
+               } elsif ($localonly && $cfg->{'s'} !~ m/p/) {
+                   zone_check_local();
+               } else {
+                   zone_check_full();
+               }
+           };
+           zone_warning("checks failed: $@",'') if length $@;
+       }
+       $output_contents{$$cfg{'output'}} .= zone_output()
+           if $install;
+
+       mail_zone_after() if $domail;
+    }
+    if ($domail) {
+    } elsif ($warnings) {
+       printf STDERR ("%s: %d warning(s) in %d zone(s);".
+                      " %d zone(s) checked ok.\n",
+                      $quis,
+                      $warnings,
+                      scalar(keys %zone_warnings),
+                      scalar(@zones - keys %zone_warnings));
+    } else {
+       progress(1, sprintf "%d zone(s) checked ok", scalar @zones);
+    }
+}
+
+use vars qw(%delgs); # $delgs{$nameserver_list} = [ $whosaidandwhy ]
+use vars qw(%auths); # $auths{$nameserver_list} = [ $whosaidandwhy ]
+use vars qw(%glue);  # $glue{$name}{$addr_list} = [ $whosaidandwhy ]
+use vars qw(%soas);  # $soa{"$serial $mname"} = [ $whosaidandwhy ]
+use vars qw(%addr_is_ok %warned);
+use vars qw($delg_to_us);
+use vars qw(@to_check); # ($addr,$whyask,$name_if_auth,$glueless_ok, ...)
+use vars qw(@to_check_soa); # ($addr,$whyask,$name,$is_ns, ...)
+
+sub zone_warning ($$) {
+    my ($w,$o) = @_;
+    my ($wk);
+
+    return 0 if !$repeat && $warned{$w}++;
+
+    $w =~ s/\n$//;
+    $w =~ s,\n, // ,g;
+
+    $w .= " ($o)" if length $o;
+    print $warn_fh "$zone: warning: $w\n" or die $!;
+    $warnings++;
+    $zone_warnings{$zone}++;
+    return 1;
+}
+
+sub zone_warnmore ($) {
+    print $warn_fh "$zone:  $_[0]\n" or die $!;
+}
+
+sub zone_check_full () {
+    zone_investigate();
+    zone_consistency();
+    zone_servers_ok();
+}
+
+sub zone_reset() {
+    %delgs= %auths= %glue= %soas= %warned= %addr_is_ok= ();
+    $delg_to_us= 0;
+    @to_check= @to_check_soa= ();
+}
+
+sub zone_investigate() {
+    my ($super_zone, @start_nsnames, $start_ww,
+       $start_ns, @start_ns_addrs, $s, $wa, $name_if_auth,
+       %nsrrset_checked, %soa_checked, $addr, $glueless_ok,
+       $rcode, $name, $is_ns);
+
+    if (!zone_style('*',0)) {
+       $super_zone= $zone;
+       for (;;) {
+           debug_trace("zone $zone superzone $super_zone");
+           $super_zone eq '.'
+               and die "no superzone ? ($super_zone)\n";
+           $super_zone =~ s/^[^.]+\.//
+               or $super_zone= '.';
+           ($rcode,@start_nsnames)=
+               lookup($super_zone,'ns-','06',"superzone search");
+           last if !$rcode;
+       }
+       $start_ww= "server for $super_zone";
+    } else {
+       ($rcode,@start_nsnames)=
+           lookup($zone,'ns-','0',"initial nameserver search");
+       $start_ww= "nameserver for $zone";
+    }
+    for $start_ns (@start_nsnames) {
+       $start_ns= lc $start_ns;
+       ($rcode,@start_ns_addrs)= lookup($start_ns,'a','0',"$start_ww");
+       foreach $addr (@start_ns_addrs) {
+           push @to_check, $addr, "$start_ns, $start_ww", undef, 0;
+       }
+    }
+    for (;;) {
+       # We do these in order so that we always do NS RRset checks on
+       # nameservers that came from other NS RRsets first; otherwise
+       # we might set nsrrset_checked due to a glueless_ok check,
+       # and then not check for gluefulness later.
+       debug_dump('@to_check @to_check_soa');
+       if (($addr,$wa,$name_if_auth,$glueless_ok,@to_check) = @to_check) {
+           push @to_check_soa, $addr, $wa, $name_if_auth, 1,
+               if defined $name_if_auth;
+           next if $nsrrset_checked{$addr}++;
+           zone_check_nsrrset($addr, $wa, $name_if_auth, $glueless_ok);
+       } elsif (($addr,$wa,$name,$is_ns,@to_check_soa) = @to_check_soa) {
+           next if $soa_checked{$addr}++;
+           zone_check_soa($addr,$wa,$name,$is_ns);
+       } else {
+           last;
+       }
+    }
+}
+
+sub zone_check_nsrrset ($$$$) {
+    my ($uaddr,$wa, $name_if_auth, $glueless_ok) = @_;
+    my (@s, $s, $a, %s2g, @glue, $glue, $delgs_or_auths, $wwn, $ww, $cg);
+    my ($rcode);
+    $ww= "[$uaddr] $wa";
+    verbose("checking delegation by $ww");
+    dig(sub {
+       if ($dig_type eq 'ns' && $dig_owner eq $zone) {
+           $s2g{lc $dig_rdata} = [ ];
+       } elsif ($dig_type eq 'a' && exists $s2g{$dig_owner}) {
+           $wwn= "in glue from $ww";
+           zone_server_queue($dig_rdata,$dig_owner,$wwn,"NS [$uaddr]",0);
+           push @{ $s2g{$dig_owner} }, $dig_rdata;
+       }
+    },
+            $zone,'ns',$uaddr);
+    if (!%s2g) {
+       zone_warning("unable to find NS RRset at [$uaddr]", $wa);
+       return;
+    } elsif (keys %s2g == 1) {
+       zone_warning("only one nameserver ". (join '', keys %s2g),
+                    $ww);
+    }
+    @s= sort keys %s2g;
+    foreach $s (@s) {
+       zone_ns_name($s,$ww);
+       @glue= @{ $s2g{$s} };
+       if (!@glue) {
+           zone_warning("glueless NS $s", $ww)
+               unless $glueless_ok
+                    or zone_style('~',!$needglue)
+                   or grep { has_suffix_of($zone,".$_"); }
+                          @{ $cfg->{'conv_glueless'} }
+                   or ((grep { has_suffix_of($s,".$_"); }
+                          @{ $cfg->{'indirect_glue'} }) and
+                        !(grep { has_suffix_of($zone,".$_"); }
+                          @{ $cfg->{'indirect_glue'} }));
+           foreach $cg (@{ $cfg->{'conv_glueless'} }) {
+               zone_warning("nameserver $s (glueless) in".
+                            " serverless-glueless namespace area $cg", $ww)
+                   if has_suffix_of(".$s",".$cg");
+           }
+           ($rcode,@glue)= lookup($s,'a','0',"glueless NS from $ww");
+           foreach $a (@glue) {
+               $wwn= "glueless NS from $ww";
+               zone_server_queue($a,$s,$wwn,"NS [$uaddr]",0);
+           }
+       }
+       $glue= join ' ', sort @glue;
+       push @{ $glue{$s}{$glue} }, $ww;
+    }
+    $s= join ' ', @s;
+    $delgs_or_auths= defined($name_if_auth) ? \%auths : \%delgs;
+    push @{ $delgs_or_auths->{$s} }, $ww;
+}
+
+sub zone_ns_name ($$) {
+    my ($name,$ww) = @_;
+    $delg_to_us=1 if grep { $name eq $_ } @{ $cfg->{'self_ns'} };
+    zone_warning("published server, as $name, but configured as stealth",
+                $ww)
+       if $cfg->{'s'} =~ m/u/ &&
+          grep { $_ eq $name }
+               @{ $cfg->{'self_ns'} }, @{ $cfg->{'self_soa'} };
+}
+
+sub zone_server_queue ($$$$$) {
+    my ($addr,$name,$wwn,$wwq,$is_soa) = @_;
+    zone_server_addr($addr,$name,$wwn,$wwq,$is_soa);
+    push @to_check, $addr, "$name, $wwn", $name, $is_soa;
+}
+
+sub zone_server_addr ($$$$$) {
+    my ($addr,$name,$ww,$wwq,$is_soa) = @_;
+    debug_trace("zone_server_addr ".join('|',@_));
+    $addr_is_ok{$addr}= "$name ($wwq)"
+       if $is_soa || $cfg->{'s'} =~ m/u/;
+    zone_warning("forbidden nameserver address [$addr] $name",$ww)
+       if grep { $_ eq $addr } @{ $cfg->{'forbid_addr'} };
+    zone_warning("forbidden server address for our slave [$addr] $name",$ww)
+       if $cfg->{'s'} =~ m/p/ and
+           grep { $_ eq $addr } @{ $cfg->{'forbid_slave'} };
+
+    my ($name_is_self, $addr_is_self);
+    $name_is_self= grep { $_ eq $name }
+        @{ $cfg->{$is_soa ? 'self_soa' : 'self_ns'} };
+    $addr_is_self= grep { $_ eq $addr }
+        @{ $cfg->{'self_addr'} };
+    if ($name_is_self && !$addr_is_self) {
+       zone_warning("our $name supplied with wrong address [$addr]", $ww);
+    }
+    if (!$name_is_self && $addr_is_self) {
+       zone_warning("we [$addr] are named in ".
+                    ($is_soa ? "SOA" : "NS").
+                    " by wrong name $name",
+                    $ww);
+    }
+    if (!$name_is_self && !$addr_is_self &&
+       $is_soa && $cfg->{'s'} =~ m/p/) {
+       zone_warning("SOA MNAME $name is not us (".
+                    (join ' ', @{ $cfg->{'self_soa'} }).")", $ww);
+    }
+    $delg_to_us=1 if $addr_is_self && !$is_soa;
+}
+
+sub zone_check_soa ($$$$) {
+    my ($uaddr,$wa,$name,$is_ns) = @_;
+    my ($lame,$serial,$mname,$got,$rcode,@soa_addrs,$soa_addr,$ww,$wwn);
+    verbose("checking service at [$uaddr] $name");
+    $lame= 'dead or lame';
+    $ww= "[$uaddr] $wa";
+    dig(sub {
+       if ($dig_type eq 'flags:') {
+           $lame= $dig_rdata =~ m/ aa / ? '' : 'lame';
+       } elsif ($dig_type eq 'soa' && $dig_owner eq $zone && !$lame) {
+           die "several SOAs ? $ww" if defined $mname;
+           $got= $dig_rdata;
+           $got =~ m/^(\d+) (\S+)$/ or die "$got ?";
+           ($serial,$mname) = ($1,$2);
+       }
+    },
+            $zone,'soa',$uaddr);
+    $lame= 'broken' if !$lame && !defined $mname;
+    if ($lame) { zone_warning("$lame server [$uaddr]",$wa); return; }
+    progress(2, sprintf "%-16s %46s has %s%s",
+            $zone, "$name [$uaddr]", $serial, $is_ns ? '' : '*');
+    push @{ $soas{$got} }, $ww;
+    ($rcode,@soa_addrs)= lookup($mname,'a','0',"SOA MNAME");
+    $wwn= "SOA MNAME from $ww";
+    foreach $soa_addr (@soa_addrs) {
+       zone_server_queue($soa_addr,$mname,$wwn,"SOA [$uaddr]",1);
+    }
+}
+
+sub zone_consistency() {
+    my ($d, $org_ser, $mname, $a, $h, $self_soa, $wa);
+    zone_consistency_set('delegations',\%delgs);
+    foreach $d (keys %delgs) { delete $auths{$d}; }
+    zone_consistency_set('zone nameserver rrset',\%auths);
+    foreach $h (keys %glue) {
+       zone_consistency_set("glue for $h", $glue{$h});
+    }
+    zone_consistency_set("serial number and/or SOA MNAME",\%soas);
+    $self_soa= $cfg->{'self_soa'};
+}
+
+sub zone_servers_ok () {
+    my ($showok,%fs);
+    if (%addr_is_ok) {
+       $showok= 0;
+       foreach $a (@{ $cfg->{'servers'} }) {
+           next if exists $addr_is_ok{$a};
+           zone_warning("we slave from [$a]",'')
+               and $showok=1;
+       }
+       if ($showok) {
+           foreach $a (keys %addr_is_ok) {
+               zone_warnmore("permitted master [$a] $addr_is_ok{$a}");
+           }
+       }
+    }
+    if ($cfg->{'s'} =~ m/s/ && !$delg_to_us) {
+       zone_warning("we are supposedly published secondary,".
+                    " but not listed as a nameserver",'');
+       map { $fs{$_}=1 } keys(%delgs), keys(%auths);
+       zone_warnmore("servers are: ". join ' ', sort keys %fs);
+    }
+}
+
+sub zone_consistency_set ($%) {
+    my ($msg,$set) = @_;
+    my ($d,$o);
+    if (keys(%$set) > 1) {
+       zone_warning("inconsistent $msg:",'');
+       foreach $d (keys %$set) {
+           foreach $o (@{ $set->{$d} }) { zone_warnmore(" $d from $o"); }
+       }
+    }
+}
+
+sub zone_check_local () {
+    zone_servers_simplefind();
+    zone_servers_ok();
+}
+
+sub zone_servers_simplefind () {
+    my ($rcode,@nsnames,$ns,@soas,$mname);
+
+    ($rcode,@nsnames)= lookup($zone,'ns-','0',"zone's servers");
+    foreach $ns (@nsnames) {
+       zone_ns_name($ns,"NS");
+       zone_server_simple($ns,'NS',0);
+    }
+    $delgs{join ' ', sort @nsnames} = [ "zone's servers" ];
+
+    ($rcode,@soas)= lookup($zone,'soa','0',"SOA MNAME");
+    die "multiple SOA RRs in set!  @soas ?" if @soas!=1;
+    $soas[0] =~ m/^(\S+)\s/ or die "SOA ? $_";
+    zone_server_simple(domain_canon($1,"lookup $zone SOA"),'SOA',1);
+}
+
+sub zone_server_simple ($$$) {
+    my ($name,$ww,$is_soa) = @_;
+    my ($rcode,@addrs,$addr);
+    ($rcode,@addrs)= lookup($name,'a','0', "server - ".
+                           ($is_soa ? "SOA MNAME" : "NS"));
+    foreach $addr (@addrs) { zone_server_addr($addr,$name,$ww,$ww,$is_soa); }
+}
+
+sub zone_style ($$) {
+    my ($stylechar, $default) = @_;
+    local ($_) = $$cfg{'s'};
+    $stylechar =~ s/\W/\\$&/ or die;
+    return m/(\S*)$stylechar/ ? $1 !~ m/\!/ : $default;
+}
+
+#-------------------- mailing
+
+use vars qw($m_base $m_lastok @m_ok @m_fail
+           $m_info $m_time $m_lock $m_m);
+
+sub mail_zone_before () {
+    my (@s1,@s2);
+
+    $m_base= $$cfg{'maildir'}.'/'.$zone;
+    $m_lastok= '-';
+    @m_ok= ();
+    @m_fail= ();
+
+    for (;;) {
+       $m_lock= new IO::File "${m_base}_lock", O_RDWR|O_CREAT, 0600
+           or die "$quis: create lockfile ${m_base}_lock: $!\n";
+       if (!flock($m_lock, LOCK_EX|LOCK_NB)) {
+           <$m_lock> =~ m/^\d+ /;
+           die "$quis: $zone: concurrrency? - flock $&$!\n";
+           $m_lock->close;
+           return 0;
+       }
+       (@s1= $m_lock->stat) or die "$quis: fstat ${m_base}_lock: $!\n";
+       (@s2= stat "${m_base}_lock") or
+           die "$quis: stat ${m_base}_lock: $!\n";
+       last if ($s1[0] eq $s2[0] && $s1[1] eq $s2[1]);
+       $m_lock->close;
+    }
+    (print $m_lock "$$        \n" and $m_lock->flush)
+       or die "$quis: write pid to ${m_base}_lock: $!\n";
+
+    if ($m_info= new IO::File "${m_base}_info", 'r') {
+       $!=0; $_= <$m_info>;
+       $_ =~ m/\n/ or die "$quis: read ${m_base}_info: $!\n";
+       m/^\d+ (\d+|-) ([0-9:]*) ([0-9:]*) / or
+           die "$quis: ${m_base}_info malformed\n";
+       $m_lastok= $1;
+       if ($domail ne 'first') {
+           if ($2 eq ':' && $3 eq ':') {
+               warn "$quis: $zone: mid/last run, but last".
+                   "run already done, ignoring zone\n";
+               return 0;
+           }
+           @m_ok= split /\:/, $2;
+           @m_fail= split /\:/, $3;
+       }
+    } elsif ($! != &ENOENT) {
+       die "$quis: open ${m_base}_info: $!\n";
+    } elsif ($domail ne 'first') {
+       warn "$quis: $zone: first run, but not --mail-first,".
+           " ignoring zone\n";
+       return 0;
+    }
+    if ($domail eq 'first') {
+       remove "${m_base}_history" or $!==&ENOENT
+           or die "$quis: remove ${m_base}_history: $!\n";
+    }
+    $progress_fh= $warn_fh= new IO::File "${m_base}_history",'a',0666
+       or die "$quis: open ${m_base}_history: $!\n";
+    $m_info= new IO::File "${m_base}_info.tmp",'w',0666
+       or die "$quis: open ${m_base}_info.tmp: $!\n";
+
+    $m_time= time;
+    progress(-1, "\n".('-'x70)."\n".ptime($m_time)."\n");
+    return 1;
+}
+
+sub mail_zone_after () {
+    if ($zone_warnings{$zone}) {
+       push @m_fail, $m_time;
+       progress(-1,"failed - $zone_warnings{$zone} warnings");
+    } else {
+       push @m_ok, $m_time;
+       $m_lastok= $m_time;
+       progress(-1,"everything is fine");
+    }
+    close $progress_fh or die "$quis: close ${m_base}_history: $!\n";
+    $progress_fh= $warn_fh= $stderr_fh;
+
+    if ($domail =~ m/^final/) {
+       if (100*@m_fail <= $$cfg{'mailmwarn'}*(@m_fail + @m_ok)) {
+           printf " %-40s ok\n", $zone or die "$quis: mail ok report: $!\n";
+       } elsif (zone_style('@',0)) {
+           printf " %-40s mail suppressed\n", $zone
+               or die "$quis: mail suppress report: $!\n";
+       } else {
+           mail_zone_mail();
+       }
+    } else {
+       printf " %-40s %d warns. OK %s Fail %s\n",
+           $zone,
+           defined $zone_warnings{$zone} ? $zone_warnings{$zone} : 0,
+           join(',', map { $_ - $m_time } @m_ok),
+           join(',', map { $_ - $m_time } @m_fail)
+               or die "$quis: checking progress report: $!\n";
+    }
+
+    @m_fail= @m_ok= ('','')
+       if $domail =~ m/^final/;
+
+    printf $m_info "%s %s %s %s \n",
+        $m_time, $m_lastok, join(':',@m_ok), join(':',@m_fail)
+           or die "$quis: write new ${m_base}_info: $!\n";
+    $m_info->close or die "$quis: close new ${m_base}_info: $!\n";
+    rename "${m_base}_info.tmp", "${m_base}_info"
+       or die "$quis: install new ${m_base}_info: $!\n";
+    $m_lock->close;
+}
+    
+
+sub pmail ($) {
+    print $m_m $_[0]
+       or die "$quis: write ${m_base}_mail: $!\n";
+}
+sub ptime ($) {
+    my ($time) = @_;
+    return gmtime($time)." GMT ($time)";
+}
+
+sub mail_zone_mail () {
+    my ($log, $zone_to, $zterr, $c, $r, $t, @soas);
+    $m_m= new IO::File "${m_base}_mail", 'w', 0666
+       or die "$quis: create ${m_base}_mail: $!\n";
+    $zone_to=''; $zterr='';
+    if (!zone_style("\$", $$cfg{'s'} !~ m/[ps]/)) {
+       eval {
+           ($r,@soas)= lookup($zone,'soa','0','problem-addr');
+           @soas==1 or die "multiple soas\n";
+           $soas[0] =~ m/^\S+ (\S.*\@\S+) [0-9 ]+$/ or
+               die "bad soa \`$_'\n";
+           $zone_to= $1;
+       };
+       $zterr= $@;
+       $zterr =~ s/\n$//;
+    }
+    pmail <<END
+From: zone checker <$$cfg{'admin'}>
+Subject: $zone - configuration problems report
+END
+;
+    pmail("To: ");
+    pmail("(testing!) ") if $domail ne 'final';
+    pmail("SOA MNAME for $zone <$zone_to>\nCC: ") if length($zone_to);
+    pmail($$cfg{'admin'}."\n\n");
+    pmail <<END
+You are receiving this mail because your email address is listed
+in the SOA (Start Of Authority) record for $zone
+in the DNS, which means you are supposedly the DNS administrator
+responsible for this zone.  This report is generated automatically
+on behalf of $$cfg{'admin'}, who
+is the administrator for a server for the zone.  Please contact
+them if you have any problem with this report.
+
+END
+if length $zone_to;
+    pmail <<END
+Sent to $$cfg{'admin'} since SOA MNAME unavailable:
+$zterr
+
+END
+if length $zterr;
+    pmail <<END
+The zone has had configuration errors or persistent operational
+problems during recent checks.  See the logs below for details.
+
+Recent check history for $zone:
+END
+;
+    if ($m_lastok ne '-') {
+       pmail(" Last seen to be fine: ".ptime($m_lastok)."\n");
+    } else {
+       pmail(" No record of this zone ever being fine.\n");
+    }
+    for $t (@m_fail) {
+       pmail(" Zone had problems at: ".ptime($t)."\n");
+    }
+    for $t (@m_ok) {
+       pmail(" Everything in order at: ".ptime($t)."\n");
+    }
+    pmail("\n");
+    $log= new IO::File "${m_base}_history",'r'
+       or die "$quis: reopen ${m_base}_history: $!";
+    undef $/;
+    pmail($log->getline);
+    $/= "\n";
+    (!$log->error and $log->close)
+       or die "$quis: reread or close ${m_base}_log: $!\n";
+    $log->eof or die;
+    $m_m->close or die "$quis: close ${m_base}_mail: $!\n";
+    $m_m= new IO::File "${m_base}_mail"
+       or die "$quis: reopen ${m_base}_mail: $!";
+
+    defined($c= fork) or die "$quis: fork for mail: $!\n";
+    if (!$c) {
+       defined dup2($m_m->fileno, 0)
+           or die "$quis - sendmail: dup for stdin: $!\n";
+       exec (qw(/usr/sbin/sendmail -odb -oee -oi),
+             ($domail eq 'final' ? '-t' : $$cfg{'admin'}));
+       die "$quis - sendmail: exec: $!\n";
+    }
+    $m_m->close;
+    $!=0; $r= waitpid $c,0;
+    $r == $c or die "$quis: waitpid sendmail ($c): $r $!";
+    $? and warn "$quis: sendmail failed: $?\n";
+
+    printf " %-40s %s\n", $zone,
+        length $zone_to ? $zone_to : 'reporting to admin'
+           or die "$quis: write mailing report: $!\n";
+}
+
+#-------------------- outputting
+
+sub zone_output () {
+    my ($o,$m);
+
+    $o= "zone \"$zone\" {\n";
+    if ($$cfg{'s'} =~ m/p/) {
+       $o.= "    type master;\n";
+    } else {
+       $o.= "    type slave;\n".
+            "    masters {\n";
+       foreach $m (@{ $$cfg{'servers'} }) { $o.= "        $m;\n"; }
+       $o.= "    };\n";
+    }
+    $o.= "    file \"$$cfg{'file'}\";\n";
+    $o.= "};\n";
+    return $o;
+}
+
+sub output_files () {
+    my ($fn,$ofn,$mfn,$l,$dir, $maxmode,$h,@to_install);
+    
+    foreach $ofn (keys %output_contents) {
+       $fn= $ofn; $mfn= "output file $fn";
+       for (;;) {
+           if (!lstat $fn) {
+               $! == &ENOENT or die "$quis: stat $mfn:\n $!\n";
+               $maxmode= 0666;
+               last;
+           } elsif (-f _) {
+               $maxmode= (stat _)[2];
+               last;
+           } elsif (-l _) {
+               defined($l= readlink $fn)
+                   or die "$quis: readlink $mfn:\n $!\n";
+               $dir= $fn =~ m,^.*/, ? $& : './';
+               $fn= "$dir$l" unless $l =~ m,^/,;
+               $mfn= "output file $fn (symlink target of $ofn)";
+           } else {
+               die "$quis: output file $mfn exists but is not a file".
+                   " (or symlink to one)";
+           }
+       }
+       unlink "$fn.new" or $! == &ENOENT or
+           die "$quis: cannot clear out old .new version of $mfn:\n $!";
+       $h= new IO::File "$fn.new",'w',$maxmode
+           or die("$quis: create .new version of $mfn:\n $!");
+       print $h
+           "# generated by $quis, do not edit\n",
+           $output_contents{$ofn}
+           or die "$quis: write data to .new version of $mfn:\n $!";
+        $h->close
+           or die "$quis: close .new version of $mfn:\n $!";
+       push @to_install, $fn,$mfn;
+    }
+
+    while (($fn,$mfn, @to_install) = @to_install) {
+       rename "$fn.new",$fn
+           or die "$quis: install new version of $mfn:\n $!";
+    }
+}
+
+#-------------------- general utilities
+
+sub debug_dump ($) {
+    my ($vn);
+    return unless $debug>1;
+    local $Data::Dumper::Terse=1;
+    foreach $vn (split /\s+/, $_[0]) {
+       print "$vn := ", eval "Dumper(\\$vn)";
+    }
+}
+
+sub debug_trace ($) {
+    return unless $debug;
+    print "D $_[0]\n";
+}
+
+sub has_suffix_of ($$) {
+    my ($whole,$suffix) = @_;
+    debug_trace("has_suffix_of $whole $suffix");
+    return 0 if length $whole < length $suffix;
+    return 0 if substr($whole, length($whole) - length($suffix)) ne $suffix;
+    debug_trace("has_suffix_of $whole $suffix YES");
+    return 1;
+}
+
+sub lookup ($$$$) {
+    my ($domain,$type,$okrcodes,$w) = @_;
+    my ($c,$h,@result);
+    debug_trace("lookup ==> (->$okrcodes) $domain $type");
+    $h= new IO::Handle;
+
+    defined($c= open $h, "-|") or die "$quis: fork adnshost:\n $!\n";
+    if (!$c) {
+       exec 'adnshost','-Fi','+Do','+Dt','+Dc','-Cf',"-t$type",
+            '-',"$domain";
+       die "$quis: exec adnshost:\n $!\n";
+    }
+    @result= $h->getlines();
+    $h->error and die "$quis: read from adnshost:\n $!\n";
+    chomp @result;
+    $!=0; $h->close;
+    die "$quis: lookup -t$type $domain $okrcodes ($w) failed $? $! @result\n"
+       if $! or $?&255 or $?>1536 or index($okrcodes,$?>>8)<0;
+    debug_trace("lookup <== $? @result");
+    return ($?,@result);
+}
+
+sub dig (&$$$) {
+    my ($eachrr, $qowner,$qtype,$qaddr) = @_;
+    # also pseudo-rr with type `flags:'
+    my ($h,$inmid,$irdata,$c,$digwhy);
+    local ($_);
+
+    debug_trace("dig ==> \@$qaddr $qowner $qtype");
+
+    $h= new IO::Handle;
+    defined($c= open $h, "-|") or die "$quis: fork dig:\n $!\n";
+    if (!$c) {
+       open STDERR, ">&STDOUT" or die $!;
+       exec ('dig',
+             '+nodef','+nosea','+norecurse',
+             "\@$qaddr",'-t',$qtype,$qowner);
+       die "$quis: exec dig:\n $!\n";
+    }
+    $inmid='';
+    for (;;) {
+       if (!defined($_= $h->getline())) {
+           $h->error() and die "$quis: read from dig:\n $!\n";
+           last;
+       }
+       chomp;
+       if (length $inmid) {
+           s/^\s+/ / or die "$inmid // $_ ?";
+           s/\;.*$//;
+           $_= $inmid.$_;
+           $inmid='';
+           s/$/ \(/ unless s/\s*\)\s*$//;
+       }
+       if (s/\s*\(\s*$//) { $inmid= $_; next; }
+       $digwhy= "dig $qowner $qtype $qaddr \`$_'";
+       if (m/^\;\; flags\:( [-0-9a-z ]+)\;/) {
+           $dig_owner=''; $dig_type='flags:'; $dig_rdata= "$1 ";
+           debug_trace("dig  f: $dig_rdata");
+           &$eachrr;
+       } elsif (m/^\;/) {
+       } elsif (!m/\S/) {
+       } elsif (m/^([-.0-9a-z]+)\s+\d\w+\s+in\s+([a-z]+)\s+(\S.*)/i) {
+           $dig_owner=domain_canon($1,$digwhy); $dig_type=lc $2; $irdata=$3;
+           if ($dig_type eq 'a') {
+               $irdata =~ m/^[.0-9]+$/ or die "$irdata ?";
+               $dig_rdata= $&;
+           } elsif ($dig_type eq 'ns') {
+               $irdata =~ m/^[-.0-9a-z]+$/i or die "bad nameserver $irdata ?";
+               $dig_rdata= domain_canon($irdata,$digwhy);
+           } elsif ($dig_type eq 'soa') {
+               $irdata =~ m/^([-.0-9a-z]+)\s+.*\s+(\d+)(?:\s+\d\w+){4}$/i
+                   or die "bad SOA $irdata ?";
+               $dig_rdata= $2.' '.domain_canon($1,$digwhy);
+           } else {
+               debug_trace("ignoring uknown RR type $dig_type");
+               next;
+           }
+           debug_trace("dig  $dig_owner $dig_type $dig_rdata");
+           &$eachrr;
+       } else {
+           debug_trace("ignoring unknown dig output $_");
+       }
+    }
+    $h->close;
+    debug_trace("dig <== gave $?");
+}
+
+sub domain_canon ($$) {
+    my ($i,$w) = @_;
+    $i =~ s/(.)\.$/$1/;
+    return '.' if $i eq '.';
+    die "domain $i ($w) ?" unless $i =~ m/^[0-9a-z]/i;
+    return lc $i;
+}
diff --git a/scripts/named-conf.8 b/scripts/named-conf.8
new file mode 100644 (file)
index 0000000..0faae0a
--- /dev/null
@@ -0,0 +1,596 @@
+.\" Hey, Emacs!  This is an -*- nroff -*- source file.
+.TH CHIARK\-NAMED\-CONF 8 "12th January 2002" "Greenend" "chiark utilities"
+.SH NAME
+chiark\-named\-conf \- check and generate nameserver configuration
+.SH SYNOPSIS
+.BR chiark\-named\-conf " [\fIoptions\fP] " \-n | \-y | \-f
+.br
+\fBchiark\-named\-conf\fP [\fIoptions\fP] \fIzone ...\fP
+.SH DESCRIPTION
+.B chiark\-named\-conf
+is a tool for managing nameserver configurations and checking for
+suspected DNS problems.  Its main functions are to check that
+delegations are appropriate and working, that secondary zones are
+slaved from the right places, and to generate a configuration for
+.BR BIND ,
+from its own input file.
+
+By default, for each zone, in addition to any warnings, the output
+lists the zone's configuration type.  If the zone is checked, the
+serial number at each of the nameservers is shown, with any
+unpublished primary having
+.B *
+after the serial number.
+.SH OPTIONS
+
+.SS MODE OPTIONS
+If one of the options
+.BR -n ", " -y ", or " -f
+is supplied then chiark-named-conf will read its main configuration
+file for the list of relevant zones.  It will then check the
+configuration and delegation for each zone
+and/or generate and install a new configuration file for
+the nameserver:
+.TP
+.BR \-y | \-\-yes
+Generate and install new nameserver config, as well as checking
+configuration, for all listed zones.
+.TP
+.BR \-n | \-\-no
+Check configuration, for all listed zones, but
+do not generate new nameserver config.
+.TP
+.BR \-f | \-\-force
+Generate and install new nameserver config, without doing any
+configuration cross-checking.  (Syntax errors in our input
+configuration will still abort this operation.)
+.TP
+.BR \-\-nothing
+Do nothing: do no checks, and don't write a new config.  This can be
+used to get a list of the zones being processed.
+.TP
+.BR \-\-mail\-first " | " \-\-mail\-middle " | " \-\-mail\-final
+Send mails to zone SOA MNAMEs reporting zones with problems.  You must
+call chiark\-named\-conf at least twice, once with \-\-mail\-first,
+and later with \-\-mail\-final, and preferably with one or more calls
+to \-\-mail\-middle in between.  All three options carry out a check
+and store the results; \-\-mail\-final also sends a mail to the zone
+SOA MNAME or local administrator, if too many of the calls had errors
+or warnings (calls before the most recent \-\-mail\-first being
+ignored).
+.TP
+.B \-mail\-final\-test
+just like \-\-mail\-final except that it always sends mail to the
+local server admin and never to remote zone contacts, adding
+.B (testing!)
+to the start of the To: field.
+.LP
+Alternatively, one or more zone names may be supplied as arguments, in
+which case their delegations will be checked, and compared with the
+data for that zone in the main configuration (if any).  In this case
+no new configuration file for the nameserver will be made.
+
+.SS ADDITIONAL OPTIONS
+.TP
+.BR \-A | \-\-all
+Checks even zones known to be broken.  Ie, ignores the
+.B ?
+zone style modifier in the configuration.
+.TP
+.BR \-C | \-\-config " \fIconfig\-file\fP"
+Use
+.I config\-file
+instead of
+.BR /etc/bind/chiark-conf-gen.zones .
+Also changes the default directory.
+.TP
+.BR \-D
+Enables debugging.  Useful for debugging chiark\-named\-conf, but
+probably not useful for debugging your DNS configuration.  Repeat to
+increase the debugging level.  (Maximum is
+.BR -DD .)
+.TP
+.BR \-g | \-\-glueless
+Do not warn about glueless referrals (strictly, makes the zone style
+modifier
+.B ~
+the default).  Not recommended - see the section GLUELESSNESS, below.
+.TP
+.BR \-l | \-\-local
+Only checks for mistakes which are the responsibility of the local
+administrator (to fix or get fixed).  This means that for published
+and stealth zones we only check that we're slaving from the right
+place and that any names and addresses for ourself are right.  For
+primary zones all checks are still done.  It is a mistake to specify
+.B \-l
+with foreign zones (zones supplied explictly on the command line but
+not relevant to the local server); doing so produces a warning.
+.TP
+.BI \-m group !*$@~?
+Overrides a
+.B modifiers
+directive in the configuration file.  The modifiers specified in the
+directive are completely replaced by those specified in this command
+line option.  (Note that modifiers specified in per-zone directives
+still override these per-group settings.)  If more than one
+.B modifiers
+directive specifies the same group, they are all affected.
+.B modifiers
+directives which don't specify a group cannot be affected.  It is an
+error if the group does not appear in the config file.  See ZONE STYLE
+MODIFIERS, below.
+.PP
+The special group
+.B foreign
+is used for zones which don't appear in the configuration file.
+.TP
+.BR \-q | \-\-quiet
+Suppress the usual report of the list of nameservers for each zone and
+the serial number from each.  When specified twice, do not print any
+information except warnings.
+.TP
+.BR \-r | \-\-repeat
+When a problem is detected, warn for all sources of the same imperfect
+data, rather than only the first we come across
+.TP
+.BR \-v | \-\-verbose
+Print additional information about what is being checked, as we go
+along.
+.SH USAGE
+The file
+.B /etc/bind/chiark-conf-gen.zones
+(or other file specified with the
+.B \-C
+option) contains a sequence of directives, one per line.  Blank lines
+are permitted.  Leading and trailing whitespace on each line is
+ignored.  Comments are lines starting with
+.BR # .
+Ending a line with a
+.BR \\
+joins it to the next line, so that long directives can be split across
+several physical lines.
+.SS GENERAL DIRECTIVES
+These directives specify general configuration details.  They should
+appear before directives specifying zones, as each will affect only
+later zone directives.  Foreign zones (zones explicitly specified on
+the command line but not mentioned in the configuration) use the
+configuration settings prevailing at the end of the config file.
+.TP
+\fBadmin\fP \fIemail\-address\fP
+Specifies the email address of the local administrator.  This is used
+in the From: line of mails sent out, and will also receive copies of
+the reports.  There is no default.
+.TP
+\fBdefault\-dir\fP \fIdirectory\fP
+Makes
+.I directory
+be the default directory (which affects the interpretation of
+relative filenames).  The default is the directory containing
+the main configuration file, ie
+.BR /etc/bind
+if no
+.B -C
+option is specified.
+.TP
+\fBforbid\-addr\fP [\fIip-address ...\fP]
+Specifies the list of addresses that are forbidden as any nameserver
+for any zone.  The default is no such addresses.
+.TP
+\fBforbid\-addr\fP [\fIip-address ...\fP]
+Specifies the list of addresses that are forbidden as a nameserver
+for a zone for which we are the primary - ie, the list of our old or
+to-be-obsoleted slaves.  The default is no such addresses.
+.TP
+\fBserverless\-glueless\fP \fIdomain ...\fP
+Specifies a list of domains under which we do not expect to find any
+nameservers without glue; for these zones it is OK to find glueless
+referrals.
+Each domain listed names a complete subtree of the DNS, starting at
+the named point.  The default is
+.BR "in\-addr.arpa ip6.arpa ip6.int" .
+
+To avoid indefinitely long or even circularly glueless referrals
+(which delay or prevent lookups) it is necessary for all sites to
+effectively implement similar conventions; currently the author
+believes that only the reverse lookup namespaces are conventionally
+devoid of (glueless) nameservers, and therefore fine to provide
+glueless referrals for.  See GLUELESSNESS below.
+.TP
+\fBallow-\-indirect\-glue\fP \fInameserver-superdomain ...\fP
+Specifies a list of domains under which we expect to find glueless
+nameservers, with up to one layer of indirection.
+For nameservers under these domains it is OK to to find glueless
+referrals, but only when listed as a nameserver for a zone which is
+not itself a subdomain of an \fBallow-indirect-glue\fR
+\fInameserver-superdomain\fR.
+
+This supports to common configuration style where DNS operator(s) set
+up all of their nameservers with names within a small subsection of
+the DNS (the portions under \fInameserver-superdomain\fRs), and
+provide glueless referrals naming these nameservers for all other
+zones.  This provides at most one level of missing glue.
+
+Note that if the DNS administrators collectively able to influence the
+service for some zone (including the admins for its superzones, the
+zones containing its nameservers, and their superzones and so forth)
+are not in sufficiently close communication do not all agree on the
+proper set of \fInameserver-superdomain\fR then they might still set
+up circular glue and \fBchiark-named-conf\fR would not necessarily be
+able to detect this even if it was run on every relevant nameserver.
+.TP
+\fBmail\-state\-dir\fP \fIdirectory\fP
+Uses
+.I directory
+for storing information about recent failures for mailing to zone
+admins.  See \-\-mail\-first et al.  Old files in here should be
+cleaned up periodically out of cron.  There is no default.
+.TP
+\fBmail\-max\-warnfreq\fP \fIpercentage\fP
+When \-\-mail\-final is used, a mail will be sent to all zones which
+had warnings or errors more than
+.IR percentage %
+of the times \-\-mail\-* was used (since the last \-\-mail\-first).
+The default is 50%.
+.TP
+.BR modifiers " " !*$@~? "] [\fIgroup\fP]"
+Applies the specified zone style modifiers (see below) to subsequently
+declared zones (until the next
+.B modifiers
+directive), as if the modifiers specified were written out for
+each zone.  You must specify at least one character for the modifiers;
+if you want to reset everything to the default, just say
+.BR ! .
+If style modifiers specified in the zone directive
+conflict with the
+.B modifiers
+directive, those specified in the zone directive take effect.
+.I group
+may contain alphanumerics and underscores, and is used for the
+.B -m
+command-line option.
+.TP
+\fBself\-addr\fP \fIip-address ...\fP
+Specifies the list of addresses that this server may be known by in
+A records.  There is no default.
+.TP
+\fBoutput\fP \fIformat\fP \fIfilename\fP [\fIformat\fP \fIfilename ...\fP]
+Arranges that each
+.I filename
+will be overwritten when
+.BR -y " or " -f
+are used; its new contents will be configuration
+directives for the zones which follow for the
+nameserver in question.  Currently the only
+.I format
+supported is
+.B bind8
+which indicates new-style BIND 8.  If no zones follow, then each
+file will still be overwritten, by an effectively empty file.
+Default: if there is no
+.B output
+directive in the configuration then the default is to use
+.BR bind8 " " chiark-conf-gen.bind8 ;
+otherwise it is an error for there to be any zones in the
+configuration before the first
+.B output
+directive.
+.TP
+\fBself\-ns\fP \fIfqdn ...\fP
+Specifies the list of names that this server may be known by in NS
+records.  There is no default.  Any trailing * is replaced by the name
+of the zone being checked, so for example
+.B self\-ns isp.ns.*
+before the zone example.com would mean to expect us to be listed as
+isp.ns.example.com
+in the NS RRset.
+.TP
+\fBself\-soa\fP \fIfqdn ...\fP
+Specifies the list of names that this server may be known by in
+the ORIGIN field of SOA records.  There is no default.  Any trailing
+* is replaced by the name of the zone, as for
+.BR self\-ns .
+.TP
+.BI self " fqdn ..."
+Equivalent to both
+.B self\-ns " and " self\-soa
+with the same set of names.
+.TP
+\fBslave\-dir\fP \fIdirectory\fP [[\fIprefix\fP] \fIsuffix\fP]
+Specifies the directory in which slave (published and stealth)
+zonefiles should be placed.  The default
+.I directory
+is
+.BR /var/cache/bind/chiark-slave .
+The default
+.IR suffix " and " prefix
+are empty; they also will be reset to these defaults by a
+.B slave\-dir
+directive which does not specify them.
+.SS ZONE DIRECTIVES
+These directives specify one or more zones.
+.TP
+.BR primary [ !*$@~? "] \fIzone filename\fP"
+Specifies that this server is supposed to be the primary nameserver
+for
+.I zone
+and that the zone data is to be found in
+.IR filename .
+.TP
+.BR primary\-dir [ !*$@~? "] \fIdirectory\fP[" / "\fIprefix\fP] [\fIsuffix\fP[" / \fIsubfile\fP]]
+Search
+.I directory
+for files whose names start with
+.I prefix
+and end with
+.IR suffix .
+Each such file is taken to represent a zone file for which this server
+is supposed to be the primary; the part of the filename between
+.IR prefix " and " suffix
+is the name of the zone.
+
+If
+.BI / subfile
+is specified, then instead of looking for files, we search for
+directories containing
+.IR subfile ;
+directories which do not contain the subfile are simply skipped.
+
+If
+.IR directory [\fB/\fP prefix ]
+exists as specified and is a directory then it is interpreted as
+.I directory
+with an empty prefix; otherwise the final path component is assumed to
+be the prefix.  If no
+.IB suffix / subfile
+is specified then the default is
+.BR _db .
+.TP
+.BR published [ !*$@~? "] \fIzone origin\-addr\fP"
+Specifies that this server is supposed to be a published slave
+nameserver for the zone in question.
+.TP
+.BR stealth [ !*$@~? "] \fIzone server\-addr ...\fP"
+Specifies that this server is supposed to be an unpublished secondary
+(aka stealth secondary) for the zone in question.
+.SS ZONE STYLE MODIFIERS
+Each of the zone directives may optionally be followed by one or more
+of the following characters (each at most once):
+.TP
+.B !
+Reverses the meaning of all style modifiers after the
+.BR ! .
+Only one
+.BR !
+must appear in the modifier list.  In this list, other modifiers which
+default to `enabled' are described by describing the effect of their
+inverse - see the description for
+.B !@
+below.
+.TP
+.B *
+Indicates that the zone is unofficial, ie that it is not delegated as
+part of the global Internet DNS and that no attempt should be made to
+find the superzone and check delegations.  Note that unofficial, local
+zones should be created with caution.  They should be in parts of the
+namespace which are reserved for private use, or belong to the actual
+zone maintainer.
+.TP
+.B $
+Indicates that any mails should be sent about the zone to the
+nameserver admin rather than to the zone SOA MNAME.  This is the
+default unless we are supposedly a published server for the zone.
+.TP
+.B !@
+Indicates that no mails should be sent about the zone to anyone.
+.TP
+.B ~
+Indicates that the zone's delegation is known to be glueless, and that
+lack of glue should not be flagged.  Not recommended - see the section
+GLUELESSNESS, below.
+.TP
+.B ?
+Indicates that the zone is known to be broken and no checks should be
+carried out on it, unless the
+.B \-A
+option is specified.
+.SS OTHER DIRECTIVES
+.TP
+\fBinclude\fP \fIfile\fP
+Reads
+.I file
+as if it were included here.
+.TP
+\fBend\fP
+Ends processing of this file; any data beyond this point is ignored.
+.SH CHECKS
+chiark\-named\-conf makes the following checks:
+
+Delegations: Each delegation from a server for the superzone should
+contain the same set of nameservers.  None of the delegations should
+lack glue.  The glue addresses should be the same in each delegation,
+and agree with the local default nameserver.
+
+Delegated servers: Each server mentioned in the delegation should have
+the same SOA record (and obviously, should be authoritative).
+
+All published nameservers - including delegated servers and servers
+named in the zone's nameserver set: All nameservers for the zone
+should supply the same list of nameservers for the zone, and none of
+this authority information should be glueless.  All the glue should
+always give the same addresses.
+
+Origin server's data: The set of nameservers in the origin server's
+version of the zone should be a superset of those in the delegations.
+
+Our zone configuration: For primary zones, the SOA origin should be
+one of the names specified with
+.BR self\-soa " (or " self ).
+For published zones, the address should be that of the SOA origin.
+For stealth zones, the address should be that of the SOA origin or one
+of the published nameservers.
+.SH GLUELESSNESS
+Glue is the name given for the addresses of nameservers which are
+often supplied in a referral.  In fact, it turns out that it is
+important for the reliability and performance of the DNS that
+referrals, in general, always come with glue.
+
+Firstly, glueless referrals usually cause extra delays looking up
+names.  BIND 8, when it receives a completely glueless referral and
+does not have the nameservers' addresses in its cache, will start
+queries for the nameserver addresses; but it will throw the original
+client's question away, so that when these queries arrive, it won't
+restart the query from where it left off.  This means that the client
+won't get its answer until it retries, typically at least 1 second
+later - longer if you have more than one nameserver listed.  Worse, if
+the nameserver to which the glueless referral points is itself under
+another glueless referral, another retry will be required.
+
+Even for better resolvers than BIND 8, long chains of glueless
+referrals can cause performance and reliability problems, turning a
+simple two or three query exchange into something needing more than a
+dozen queries.
+
+Even worse, one might accidentally create a set of circularly glueless
+referrals such as
+.br
+.B example.com NS ns0.example.net.uk
+.br
+.B example.com NS ns1.example.net.uk
+.br
+.B example.net.uk NS ns0.example.com
+.br
+.B example.net.uk NS ns1.example.com
+.br
+Here it is impossible to look up anything in either example.com or
+example.net.uk.
+
+There are, as far as the author is aware, no generally agreed
+conventions or standards for avoiding unreasonably long glueless
+chains, or even circular glueless situations.  The only way to
+guarantee that things will work properly is therefore to always supply
+glue.
+
+However, the situation is further complicated by the fact that many
+implementations (including BIND 8.2.3, and many registry systems),
+will refuse to accept glue RRs for delegations in a parent zonefile
+unless they are under the parent's zone apex.  In these cases it can
+be necessary to create names for the child's nameservers which are
+underneath the child's apex, so that the glue records are both in the
+parent's bailiwick and obviously necessary.
+
+In the past, the `shared registry system' managing .com, .net and .org
+did not allow a single IPv4 address to be used for more than one
+nameserver name.  However, at the time of writing (October 2002) this
+problem seems to have been fixed, and the workaround I previously
+recommended (creating a single name for your nameserver somewhere
+in .com, .net or .org, and using that for all the delegations
+from .com, .net and .org) should now be avoided.
+
+Finally, a note about `reverse' zones, such as those in in-addr.arpa:
+It does not seem at all common practice to create nameservers in
+in-addr.arpa zones (ie, no NS RRs seem to point into in-addr.arpa,
+even those for in-addr.arpa zones).  Current practice seems to be to
+always use nameservers for in-addr.arpa which are in the normal,
+forward, address space.  If everyone sticks to the rule of always
+publishing nameservers names in the `main' part of the namespace, and
+publishing glue for them, there is no chance of anything longer than a
+1-step glueless chain might occur for a in-addr.arpa zone.  It is
+probably best to maintain this as the status quo, despite the
+performance problem this implies for BIND 8 caches.  This is what the
+serverless\-glueless directive is for.
+
+Dan Bernstein has some information and examples about this at
+.UR http://cr.yp.to/djbdns/notes.html#gluelessness
+http://cr.yp.to/djbdns/notes.html#gluelessness
+.UE
+but be warned that it is rather opinionated.
+.SS GLUELESSNESS SUMMARY
+
+I recommend that every nameserver should have its own name in every
+forward zone that it serves.  For example:
+.br
+.B zone.example.com NS servus.ns.example.com
+.br
+.B servus.ns.example.com A 127.0.0.2
+.br
+.B 2.0.0.127.in-addr.arpa PTR servus.example.net
+.br
+.B servus.example.net A 127.0.0.2
+.LP
+Domain names in
+.B in-addr.arpa
+should not be used in the right hand side of NS records.
+.SH SECURITY
+chiark\-named\-conf is supposed to be resistant to malicious data in
+the DNS.  It is not resistant to malicious data in its own options,
+configuration file or environment.  It is not supposed to read its
+stdin, but is not guaranteed to be safe if stdin is dangerous.
+.LP
+Killing chiark-named-conf suddenly should be safe, even with
+.BR -y " or " -f
+(though of course it may not complete its task if killed), provided
+that only one invocation is made at once.
+.LP
+Slow remote nameservers will cause chiark-named-conf to take
+excessively long.
+.SH EXIT STATUS
+.TP
+.B 0
+All went well and there were no warnings.
+.TP
+any other
+There were warnings or errors.
+.SH FILES
+.TP
+.B /etc/bind/chiark-conf-gen.zones
+Default input configuration file.  (Override with
+.BR -C .)
+.TP
+.B /etc/bind
+Default directory.  (Override with
+.BR -C " or " default\-dir .)
+.TP
+.IB dir /chiark-conf-gen.bind8
+Default output file.
+.TP
+.B /var/cache/bind/chiark-slave
+Default location for slave zones.
+.SH ENVIRONMENT
+.LP
+Setting variables used by
+.BR dig (1)
+and
+.BR adnshost (1)
+will affect the operation of chiark\-named\-conf.  
+Avoid messing with these if possible.
+.LP
+.B PATH
+is used to find subprograms such as
+.BR dig " and " adnshost .
+.SH BUGS
+The determination of the parent zone for each zone to be checked, and
+its nameservers, is done simply using the system default nameserver.
+
+The processing of output from
+.B dig
+is not very reliable or robust, but this is mainly the fault of dig.
+This can lead to somewhat unhelpful error reporting for lookup
+failures.
+.SH AUTHOR
+.B chiark\-named\-conf
+and this manpage were written by Ian Jackson
+<ian@chiark.greenend.org.uk>.  They are Copyright 2002 Ian Jackson.
+
+chiark\-named\-conf and this manpage are 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 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, consult the Free Software Foundation's
+website at www.fsf.org, or the GNU Project website at www.gnu.org.
diff --git a/scripts/ngspice2genspic b/scripts/ngspice2genspic
new file mode 100755 (executable)
index 0000000..1d80d60
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+#   ngspice2genspic - Copyright 2004 Ian Jackson - see below
+#
+# Reads the output from ngspice and outputs a `genspic' file
+# for use with genspic2gnuplot.  Takes no arguments or options.
+#
+# Limitations
+#
+#  Only frequency (.AC) and transient plots have been tested.
+#
+#  Displaying voltages and currents on the same .TRAN graph won't work
+#   well because they currently have to have the same Y scale.
+#
+#  This whole scheme is very clumsy.  There should be a driver program
+#  with command-line option parsing.
+
+# This program and its documentation 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 3, or (at your option) any later version.
+# 
+# This program and its documentation are distributed in the hope that
+# they 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+die if @ARGV;
+
+while (<STDIN>) {
+    if (m/^Total CPU/) {
+       die if $mode ne '';
+       $seentcpu=1;
+    } elsif (m/^Current dynamic memory/ ||
+            m/^Dynamic memory limit/ ||
+            m/^Level not specified/ ||
+            m/^Doing analysis/ ||
+            m/^CPU time since/ ||
+            m/^Circuit\:/) {
+       die if length $mode;
+    } elsif (m/\f/) {
+       die if m/\S/;
+    } elsif (!m/\S/ && $mode eq InitTransSkip) {
+       $mode= '';
+    } elsif (m/^Initial Transient/ && $mode eq '') {
+       $mode= InitTrans;
+    } elsif (m/^Node/ && $mode eq InitTrans) {
+       $mode= InitTransSkip;
+    } elsif (m/\w/ && $mode eq InitTransSkip) {
+    } elsif (m/^\-\-/ && length $mode) {
+    } elsif (m/^\s\s+(\w+) Analysis /) {
+       if ($mode eq '') {
+           $mode= Table;
+           $ctable= $1;
+           $dokey= 1;
+       } else {
+           $dokey= 0;
+       }
+    } elsif (m/^\s\s+/ && ($mode eq Table or !length $mode)) {
+    } elsif (m/^Index / && $mode eq Table) {
+       s/\s+$//;
+       ($index,$key,@nowheadings) = split /\s+/, $_;
+       $logxy= 0+($key eq 'frequency');
+    } elsif (m/^\d+\s/ && $mode eq Table) {
+       ($index,$key,@nowcolumns) = split /\s+/, $_;
+       shift @nowcolumns if $key =~ s/\,$//;
+       $coldata[$index] .= " $key" if $dokey;
+       $coldata[$index] .= " @nowcolumns";
+       if (!$index) {
+           foreach $c (@nowheadings) {
+               $h= 'y';
+               $h= 'y2' if $c =~ m/^ph\(/;
+               push @columns, "$h:$c";
+           }
+       }
+    } elsif (!m/\S/ && $mode eq Table) {
+       print "S $ctable $logxy @columns\n" or die $!;
+       foreach $l (@coldata) { printf "D%s\n", $l or die $!; }
+       print "T\n" or die $!;
+       $mode= '';
+       @columns= ();
+       @coldata= ();
+    } elsif (!m/\S/) {
+       $mode= '' if $mode eq InitTransSkip;
+    } else {
+       die "$mode: $_ ?";
+    }
+}
+die "no plots" unless defined $ctable;
+print "F\n" or die $!;
+
+# $Id: ngspice2genspic,v 1.3 2007-09-21 21:21:15 ianmdlvl Exp $
diff --git a/scripts/nntpid b/scripts/nntpid
new file mode 100755 (executable)
index 0000000..e0e286a
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+# Originally by Simon Tatham
+# Modified by Richard Kettlewell, Colin Watson, Ian Jackson
+
+use ChiarkNNTP;
+
+our $verbose;
+($verbose='STDERR', shift @ARGV) if $ARGV[0] eq "-v";
+
+my $c = cnntp_connect($verbose);
+$c->banner_reader();
+
+our $code;
+
+# some servers require a GROUP before an ARTICLE command
+$c->docmd("GROUP misc.misc");
+
+if(@ARGV == 0) {
+  while(<>) {
+    s/(^\s+|\s+$)//gs;
+    lookup($_);
+  }
+} else {
+  while (@ARGV) {
+    my $item = shift @ARGV;
+    if($item !~ /[\@:]/ and not defined $group) {
+      # maybe a bare group followed by an article number
+      die unless @ARGV;
+      my $number = shift @ARGV;
+      $item = "$item $number";
+    }
+    lookup($item);
+  }
+}
+
+$c->docmd("QUIT");
+close S;
+
+sub lookup {
+  my $mid = shift;
+
+  if($mid !~ /\@/ and $mid =~ /^(.*)[: ](\d+)$/) {
+      my ($g, $n) = ($1, $2);
+      $c->docmd("GROUP $g");
+      $c->docmd("ARTICLE $n");
+  } else {
+      $mid =~ s/.*\<//;
+      $mid =~ s/\>.*//;
+      $c->docmd("ARTICLE <$mid>");
+  }
+
+  my $fh= 'STDOUT';
+  if (-t $fh) {
+    my $lesscmd= $ENV{'NNTPID_PAGER'};
+    $lesscmd= 'less' unless defined $lesscmd;
+    open LESS, "|-", 'sh','-c',$lesscmd or die $!;
+    $fh= 'LESS';
+  }
+  
+  while (1) {
+    ($code,$_) = $c->getline();
+    s/[\r\n]//g;
+    last if /^\.$/;
+    s/^\.//;
+    print $fh "$_\n";
+  }
+
+  if ($fh ne 'STDOUT') {
+    close $fh or die "$? $!";
+  }
+}
diff --git a/scripts/palm-datebook-reminders b/scripts/palm-datebook-reminders
new file mode 100755 (executable)
index 0000000..2d81e46
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/bin/perl
+
+# Copyright 2003 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This script and its documentation (if any) are free software; you
+# can redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+use Palm::PDB;
+use Palm::Datebook;
+use Time::Local;
+use POSIX;
+
+$us= $0 =~ m,[^/]+$, ? $& : $0;
+
+$detail= 86400*2;
+$lookforward= 86400*(7*4+1);
+# run at 15:00 daily
+
+sub setfilename($) {
+    die "$us: only one filename at a time please\n" if defined $filename;
+    $filename= $_[0];
+}
+
+while (@ARGV) {
+    $_= shift @ARGV;
+    if (m,^(?:\-t)?(\d+)/(\d+)$,) {
+       ($detail,$lookforward)=($1+0,$2+0);
+    } elsif (m,^[./],) {
+       setfilename($_);
+    } elsif (m/^\-f/) {
+       setfilename($');
+    } else {
+       die "$us: unknown argument/option \`$_'\n";
+    }
+}
+
+$filename= "DatebookDB.pdb" if !defined $filename;
+
+defined($now= time) or die $!;
+stat $filename or die "$us: $filename: $!\n";
+$backuptime= (stat _)[10];
+
+$pdb = new Palm::PDB or die $!;
+$pdb->Load($filename) or die $!;
+
+
+
+foreach $record (@{ $pdb->{records} }) {
+    if ($record->{start_hour} != 255) {
+       $timestr= sprintf("%02d:%02d-%02d:%02d",
+                         $record->{start_hour},
+                         $record->{start_minute},
+                         $record->{end_hour},
+                         $record->{end_minute});
+       @lt=(0, $record->{start_minute}, $record->{start_hour});
+    } else {
+       @lt=(0, 0, 9);
+       $timestr= "           ";
+    }
+    if ($record->{repeat}{type}) {
+       $timestr= "onwards    ";
+    }
+    
+    push @lt, $record->{day}, $record->{month}-1,  $record->{year}-1900;
+    defined($ettt= timelocal @lt) or die $!;
+
+    next if ($ettt < $now - 86400 ||
+            $ettt > $now + $lookforward);
+
+    @lt2= localtime($ettt) or die $!;
+    defined($dowstr= strftime "%a", @lt2) or die $!;
+
+    $datestr= sprintf("%04d-%02d-%02d",
+                     $record->{year},
+                     $record->{month},
+                     $record->{day});
+    
+    $timesortkey= "$datestr $timestr";
+    $evhead= "$datestr $dowstr $timestr";
+
+    $desc= $record->{description};
+    $desc =~ s/\x93/\~/g;
+    $desc =~ s/\xa3/L/g;
+    $desc =~ s/[^\n -\176]/ sprintf "\\x%02x", ord $& /eg;
+    $desc =~ s/^ +//;
+    $desc =~ s/\n+$//;
+    $desc .= "\n";
+
+    if ($desc =~ m/^(.{0,51})\n/) {
+       $descsumm= $1;
+    } elsif ($desc =~ m/^.{48}/) {
+       $descsumm= $&.'...';
+    } else {
+       $descsumm= " --- no description ?! ---";
+    }
+    $kind= $ettt < $now + $detail ? 'detail' : 'forward';
+
+    push @{ $events{$kind} }, {
+       TSK => $timesortkey,
+       Headline => sprintf("%s  %s", $evhead, $descsumm),
+       Desc => $desc
+       };
+}
+
+sub sectline_detail(){ return "Imminent events"; }
+sub sectline_forward(){ return "Forthcoming events"; }
+
+sub headline(){
+    printf("%s\n", $ev->{Headline})
+       or die $!;
+}
+
+sub print_forward(){ headline(); }
+sub print_detail(){
+    print "\n" or die $!;
+    headline();
+    $_= $ev->{Desc};
+    s/^/    /gm;
+    print $_ or die $!;
+}
+
+foreach $kind (qw(detail forward)) {
+    $sectline= &{"sectline_$kind"};
+    printf("%s\n%s\n",
+          $sectline,
+          '-'x(length $sectline))
+       or die $!;
+    if (!@{ $events{$kind} }) {
+       printf("None scheduled.\n")
+           or die $!;
+    }
+    foreach $ev (sort { $a->{TSK} cmp $b->{TSK} } @{ $events{$kind} }) {
+       &{"print_$kind"};
+    }
+    print "\n" or die $!;
+}
+
+@lt2= localtime $backuptime or die $!;
+defined ($syncstr= strftime "%Y-%m-%d %a %H:%M", @lt2) or die $!;
+
+print("Date of last synch: $syncstr\n".
+      "Events entered on PDA after this date are omitted.\n")
+    or die $!;
+       
+close STDOUT or die $!;
diff --git a/scripts/palm-datebook-reminders.1 b/scripts/palm-datebook-reminders.1
new file mode 100644 (file)
index 0000000..40a9301
--- /dev/null
@@ -0,0 +1,66 @@
+.\" Hey, Emacs!  This is an -*- nroff -*- source file.
+.TH PALM\-DATEBOOK\-REMINDERS 1 "12th January 2002" "Greenend" "chiark utilities"
+.SH NAME
+palm\-datebook\-reminders \- generate simple report from Palm Datebook DB
+.SH SYNOPSIS
+.BR PALM\-DATEBOOK\-REMINDERS " [\fIoptions\fP]
+.SH DESCRIPTION
+.B palm\-datebook\-reminders
+reads a Palm Datebook database and prints out a list of events in the
+near future in a reasonably easy to read format.
+.PP
+Immiment events (ones in approximately the next 48 hours by default)
+are listed with complete item text.  Forthcoming events (approximately
+the next 4 weeks by default) are listed in summary form.
+.SH OPTIONS
+.TP
+.IR imminent \fB/\fR forthcoming " | " \fB-t\fR imminent \fB/\fR forthcoming
+Specifies that imminent and forthcoming events are ones within the next
+.I immiment
+and
+.I forthcoming
+seconds, respectively.
+.TP
+.IR filename " | \fB-f\fR" filename
+Reads the file
+.I filename
+instead of
+.BR ./DatebookDB.pdb .
+If the bare form is used,
+.I filename
+must start with a
+.B .
+or
+.B /
+(dot or slash).
+.SH BUGS
+The program is not configurable enough.
+
+The option parsing is very grotty.
+
+Proper character set conversion is not performed.
+.SH FILES
+.TP
+.BR DatebookDB.pdb
+Default input file.
+.SH AUTHOR
+.B PALM\-DATEBOOK\-REMINDERS
+and this manpage were written by Ian Jackson
+<ian@chiark.greenend.org.uk>.  They are Copyright 2003 Ian Jackson.
+
+palm\-datebook\-reminders and this manpage are 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 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, consult the Free Software Foundation's
+website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+Palm Datebook is a piece of proprietary software supplied with Palm
+organisers.  Palm and Datebook are probably trademarks.
diff --git a/scripts/random-word b/scripts/random-word
new file mode 100755 (executable)
index 0000000..66d09a2
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/perl
+
+# Copyright 2004 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This script and its documentation (if any) are free software; you
+# can redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+use IO::Handle;
+use IO::File;
+use POSIX;
+
+$want= 1;
+$filename= "/usr/share/dict/words";
+@randfile= ("/dev/urandom", "/dev/random");
+
+sub fail ($) { die "random-word: $_[0]\n"; }
+open D, ">/dev/null" or fail("open /dev/null: $!");
+
+while ($ARGV[0] =~ m/^\-/) {
+    $_= shift @ARGV;
+    if (m/^\-\-?$/) {
+       last;
+    } elsif (m/^\-n(\d+)$/) {
+       $want= $1;
+    } elsif (m/^\-f/ && length > 2) {
+       $filename= $';
+    } elsif (m/^\-r/ && length > 2) {
+       @randfile= ($');
+    } elsif (m/^\-D$/) {
+       open D, ">&STDERR" or fail("dup stderr for debug: $!");
+    } else {
+       fail("unknown option \`$_'");
+    }
+}
+
+sub debug ($) {
+    print D "random-word: debug: $_[0]\n"
+       or fail("write debug: $!");
+}
+
+for $randfile (@randfile) {
+    $r= new IO::File "$randfile", 'r';
+    debug("open $randfile ".($r ? "ok" : "failed $!"));
+    last if $r;
+    $!==&ENOENT or fail("cannot open $randfile: $!");
+}
+$r or fail("could not open any random device: $!\n (tried @randfile)");
+$r->autoflush(0);
+
+$w= new IO::File $filename, 'r';
+$w or fail("cannot open $filename: $!");
+debug("open $filename ok");
+@words= <$w>;
+$w->error and fail("cannot read $filename: $!");
+debug("read $filename ok");
+
+while (@out < $want) {
+    $!=0; read $r,$rbytes,4;
+    length $rbytes==4 or fail("cannot read $randfile: $!");
+    $wordno= unpack 'L',$rbytes;
+    $wordno &= ~0x80000000;
+    $wordno %= @words;
+    $_= $words[$wordno];
+    chomp;
+    debug("picked $wordno \`$_'");
+    next unless m/^([a-z][-a-z]+)$/;
+    push @out, $1;
+    debug("good, now ".scalar @out);
+}
+
+debug("enough");
+
+print join(' ',@out), "\n"
+    or fail("cannot write output: $!");
diff --git a/scripts/remountresizereiserfs b/scripts/remountresizereiserfs
new file mode 100755 (executable)
index 0000000..810a51e
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/bash
+# usage:
+#   remountresizereiserfs /mountpoint
+
+set -e
+fail () { echo >&2 "$*"; exit 1; }
+case "$#.$1" in 1.[^-]*);; *) fail 'bad usage';; esac
+
+mp=$1
+
+df=`df -P $mp`
+dfl2=`printf "%s" "$df" | sed 1d`
+
+case "$dfl2" in
+/dev/*" "[0-9]*" "[0-9]*" "[0-9]*" "[0-9]*"% "/*)
+       dev=${dfl2%% *}
+       mp2=${dfl2##* }
+       if [ "x$mp2" != "x$mp" ]; then fail "mountpoint is $mp2 not $mp"; fi
+       ;;
+*)     fail "could not parse df output" ;;
+esac
+
+dm=/dev/mapper
+case "$dev" in
+$dm/*/*)
+       fail "too many path segments in mapper device \`$dev'"
+       ;;
+$dm/*)
+       lv=${dev#$dm/}
+       lv=${lv//--//}
+       case "$lv" in
+       *-*)    ;;
+       *)      fail "no single hyphen in mapper device \`$lv'";;
+       esac
+       vg=${lv%%-*}
+       lv=${lv#*-}
+       vg=${vg//\//-}
+       lv=${lv//\//-}
+       devu=/dev/$vg/$lv
+       ;;
+*)
+       devu=$dev
+esac
+
+lvi=$(lvdisplay -c $devu)
+vg=${lvi#*:}
+vg=${vg%%:*}
+vgsz_kb=${lvi#*:*:*:*:*:*:}
+vgsz_kb=${vgsz_kb%%:*}
+
+dbrfs=$(debugreiserfs $dev)
+blksz_by=$(printf "%s" "$dbrfs" | egrep '^Blocksize: ' || fail "blocksize?")
+blksz_by=${blksz_by#*: }
+
+vgsz_blk=$(dc -e "$vgsz_kb 1024* $blksz_by /p")
+
+echo mount -o remount,resize=$vgsz_blk $mp
diff --git a/scripts/summarise-mailbox-preserving-privacy b/scripts/summarise-mailbox-preserving-privacy
new file mode 100755 (executable)
index 0000000..fd19090
--- /dev/null
@@ -0,0 +1,171 @@
+#!/usr/bin/perl
+# usage:
+#     summarise-mailbox-preserving-privacy \
+#          [<our options>] [--] <email>
+# our options:
+#  -S<subject>       default: "summary of messages on <mailname>"
+#  -F<state file>    default: $HOME/.summarise-mailbox/last<from options>
+#  -f<mailbox>       passed to from(1)  must use -F if it contains / or |
+#  -s<sender>        passed to from(1)  must use -F if it contains / or |
+#  -q                throw away stderr and always exit 0
+#  --          end of our options
+
+# Copyright 2006 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This script and its documentation (if any) are free software; you
+# can redistribute it and/or modify them under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 3, or (at your option) any later version.
+# 
+# chiark-named-conf and its manpage are 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+
+use strict (qw(refs));
+use POSIX;
+use IO::Handle;
+use Fcntl (qw(:flock));
+
+@from_options=();
+$sendmail= '/usr/sbin/sendmail -odi -oee -oi -t';
+
+umask 077;
+while (@ARGV && $ARGV[0] =~ m/\-/) {
+    $_= shift;
+    if (m/^\-[fs]/) {
+       push @from_options, $_;
+    } elsif (s/^\-S//) {
+       $subject= $_;
+    } elsif (s/^\-F//) {
+       $statefile= $_;
+    } elsif (s/^\-q$//) {
+       $quiet= 1;
+       open STDERR, ">/dev/null";
+       eval('END { $?=0; }');
+    } elsif (m/^\-\-$/) {
+       last;
+    } else {
+       die "$0: unknown option \`$_'\n";
+    }
+}
+die unless @ARGV==1;
+$emailto= shift @ARGV;
+
+unless (defined $subject) {
+    my ($our_hostname);
+    open M, "/etc/mailname" or die $!;
+    defined($our_hostname= <M>) or die $!;
+    chomp($our_hostname);
+    close M;
+    $subject= "summary of messages on $our_hostname";
+}
+
+unless (defined $statefile) {
+    my ($dir);
+    die "$0: -F needed with that -f or -s\n"
+       if grep m:[|/]:, @from_options;
+    die "$0: no HOME in environment\n" unless defined $ENV{'HOME'};
+    $dir= $ENV{'HOME'}.'/.summarise-mailbox';
+    mkdir $dir, 02700 or $!==&EEXIST or die "$dir: $!";
+    $statefile= $dir.'/last'.
+       join('|',@from_options);
+}
+
+$statefile= "./$statefile" unless $statefile =~ m,^/,;
+$lockfile= $statefile.'.lock';
+$errfile= $statefile.'.err';
+
+open L, "+> $lockfile" or die "$lockfile: $!";
+flock L, LOCK_EX or die "$lockfile: $!";
+
+if ($quiet) {
+    open STDERR, "> $errfile";
+}
+
+sub parse($) {
+    my ($incr, $lasttime) = @_;
+    $lasttime= '';
+    while (defined($_= <F>)) {
+       print N or die "$statefile.new: $!" if $incr>0;
+       $have{$_} += $incr;
+       $have += $incr;
+       m/^From .* (\w+ \w+ \d+ [0-9:]+ \d+)$/ or die "$_ ?";
+       $lasttime= $1;
+    }
+    die $! if F->error;
+    return $lasttime;
+}
+
+if (open F, "< $statefile\0") {
+    $old_lasttime= parse(-1);
+    close F or die $!;
+} elsif ($! != &ENOENT) {
+    die "$statefile $!";
+}
+
+open N, "> $statefile.new" or die "$statefile.new: $!";
+
+$child= open F, "-|"; defined $child or die $!;
+if (!$child) {
+    exec "from",@from_options;
+    die "$!";
+}
+
+$did_have= $have;
+$new_lasttime= parse(+1);
+$?=0; close F or die "$? $!";
+
+close N or die "$statefile.new: $!";
+
+if ($new_lasttime ne $old_lasttime and $new_lasttime ne '') {
+    push @reasons, "Timestamp of last message in mailbox changed.";
+}
+
+map { $total_more+=$_ if $_>0 } values %have;
+
+if ($total_more) {
+    push @reasons, "$total_more message(s)".
+       " which were not previously present.";
+} elsif ($have>0) {
+    push @reasons, "More messages than previously reported.";
+}
+
+exit 0 unless @reasons;
+
+$new_have= $have - $did_have;
+
+$msg= <<END
+To: $emailto
+Subject: $subject
+
+Regarding your mailbox @from_options:
+
+Changes detected since last report:
+END
+    ;
+
+$msg .= join "\n", map { "    $_" } @reasons;
+
+$msg .= <<END
+
+
+Now:
+    There are $new_have message(s).
+    The last is dated: $new_lasttime.
+
+--
+generated by $0
+END
+    ;
+
+open S, "| $sendmail" or die $!;
+print S $msg or die $!;
+$?=0; close S or die "$? $!";
+
+rename "$statefile.new", $statefile or die $!;
diff --git a/settings.make b/settings.make
new file mode 100644 (file)
index 0000000..5471dbd
--- /dev/null
@@ -0,0 +1,57 @@
+# Makefile
+# common make settings
+#
+# This file is part of chiark-utils, a collection of useful utilities
+#
+# This file is:
+#  Copyright (C) 1997-1998,2000-2001 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+CONFIG_CPPFLAGS=       -DRWBUFFER_SIZE_MB=$(RWBUFFER_SIZE_MB) \
+                       -DREALLY_CHECK_FILE='"/etc/inittab"'
+
+CC=            gcc
+CFLAGS=                $(WARNINGS) $(OPTIMISE) $(DEBUG)
+CPPFLAGS=      $(CONFIG_CPPFLAGS)
+
+WARNINGS=      -Wall -Wwrite-strings -Wmissing-prototypes \
+               -Wstrict-prototypes -Wpointer-arith -Wno-pointer-sign
+OPTIMISE=      -O2
+DEBUG=         -g
+
+SYSTEM_GROUP=  root
+
+prefix=/usr/local
+etcdir=/etc
+varlib=/var/lib
+
+confdir=$(etcdir)/$(us)
+bindir=$(prefix)/bin
+sbindir=$(prefix)/sbin
+sharedir=$(prefix)/share/$(us)
+txtdocdir=$(prefix)/share/doc/$(us)
+exampledir=$(txtdocdir)/examples
+vardir=$(varlib)/$(us)
+mandir=${prefix}/man
+man1dir=${mandir}/man1
+man8dir=${mandir}/man8
+
+# INSTALL_PROGRAM_STRIP_OPT=-s
+
+INSTALL=               install -c
+INSTALL_SHARE=         $(INSTALL) -m 644 -o root -g $(SYSTEM_GROUP)
+INSTALL_SCRIPT=                $(INSTALL) -m 755 -o root -g $(SYSTEM_GROUP)
+INSTALL_PROGRAM=       $(INSTALL_SCRIPT) $(INSTALL_PROGRAM_STRIP_OPT)
+INSTALL_DIRECTORY=     $(INSTALL) -m 2755 -o root -g $(SYSTEM_GROUP) -d
diff --git a/sync-accounts/Makefile b/sync-accounts/Makefile
new file mode 100644 (file)
index 0000000..f9279e2
--- /dev/null
@@ -0,0 +1,66 @@
+# Makefile
+
+# This file is part of chiark-utils, a collection of useful programs
+# used on chiark.greenend.org.uk.
+#
+# This file is:
+#  Copyright 2001-2002 Ian Jackson <ian@chiark.greenend.org.uk>
+#
+# This 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 3, or (at your option) any later version.
+#
+# This 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+
+SYSTEM_GROUP=  root
+
+INSTALL=               install -c
+INSTALL_SHARE=         $(INSTALL) -m 644 -o root -g $(SYSTEM_GROUP)
+INSTALL_SCRIPT=                $(INSTALL) -m 755 -o root -g $(SYSTEM_GROUP)
+INSTALL_DIRECTORY=     $(INSTALL) -m 2755 -o root -g $(SYSTEM_GROUP) -d
+
+prefix=/usr/local
+bindir=$(prefix)/bin
+mandir=${prefix}/man
+man5dir=${mandir}/man5
+man8dir=${mandir}/man8
+txtdocdir=$(prefix)/share/doc/sync-accounts
+exampledir=$(txtdocdir)/examples
+
+SCRIPTS=       sync-accounts sync-accounts-createuser grab-account
+MANPAGES5=     sync-accounts
+MANPAGES8=     sync-accounts sync-accounts-createuser grab-account
+EXAMPLES=      linux bsd
+
+all:
+
+install:
+               $(INSTALL_DIRECTORY) $(bindir)
+               set -e; for f in $(SCRIPTS); do \
+                       $(INSTALL_SCRIPT) $$f $(bindir)/$$f; done
+
+install-docs:
+               $(INSTALL_DIRECTORY) $(man5dir) $(man8dir)
+               set -e; for f in $(MANPAGES5); do \
+                       $(INSTALL_SHARE) $$f.5 $(man5dir)/$$f.5; done
+               set -e; for f in $(MANPAGES8); do \
+                       $(INSTALL_SHARE) $$f.8 $(man8dir)/$$f.8; done
+
+install-examples:
+               $(INSTALL_DIRECTORY) $(exampledir)
+               set -e; for e in $(EXAMPLES); do \
+                       $(INSTALL_SHARE) sync-accounts.example-$$e \
+                               $(exampledir)/sync-accounts.$$e; \
+                       done
+
+clean:
+               rm -f *~ ./#*#
+
+distclean realclean:   clean
diff --git a/sync-accounts/grab-account b/sync-accounts/grab-account
new file mode 100755 (executable)
index 0000000..8003215
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+# This is part of sync-accounts, a tool for synchronising UN*X password data.
+#
+# sync-accounts is
+#  Copyright 1999-2000,2002 Ian Jackson <ian@davenant.greenend.org.uk>
+#  Copyright 2000-2001 nCipher Corporation Ltd
+#
+#  sync-accounts 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 3, or (at
+#  your option) any later version.
+#
+#  sync-accounts 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 already have a copy of the GNU General Public License.
+#  If not, consult the Free Software Foundation's website at
+#  www.fsf.org, or the GNU Project website at www.gnu.org.
+#
+# $Id: grab-account,v 1.5 2007-09-21 21:21:15 ianmdlvl Exp $
+
+set -e
+
+if [ $# -lt 2 -o $# -gt 3 ]
+then
+       echo >&2 \
+'usage: grab-account <localuser> <shorthostname> [<remoteuser>]
+creates an entry in /etc/sync-accounts, and runs sync-accounts
+$Id: grab-account,v 1.5 2007-09-21 21:21:15 ianmdlvl Exp $'
+       exit 1
+fi
+
+lu="$1"
+sh="$2"
+
+if [ $# -gt 2 ]
+then
+       ru="$3"
+else
+       ru="$1"
+fi
+
+cf=/etc/sync-accounts
+
+if perl -ne 'exit 1 if m/^\s*user\s+'$lu'\s/;' <$cf
+then
+       perl -pe '
+               next unless m/^\s*host\s+'$sh'\s*$/...m/^host|^end/;
+               next unless m/^\s*addhere\s*$/;
+               next if $done++;
+               print "user '$lu'".("'$lu'" eq "'$ru'" ? "" : " remote='$ru'")."\n"
+                       or die $!;
+        END {
+               print(STDERR "\`addhere'\'' line not found\n"), $?=1 if !$? && !$done;
+        }
+               ' $cf >$cf.new
+       mv -f $cf.new $cf
+else
+       echo "entry already exists in $cf, leaving alone"
+fi
+
+sync-accounts $sh
diff --git a/sync-accounts/grab-account.8 b/sync-accounts/grab-account.8
new file mode 100644 (file)
index 0000000..e5822dc
--- /dev/null
@@ -0,0 +1,77 @@
+.\" Hey, Emacs!  This is an -*- nroff -*- source file.
+.TH GRAB\-ACCOUNT 8 "14th July 2002" "Greenend" "chiark utilities"
+.SH NAME
+grab\-account \- add new account synchronised to remote system
+.SH SYNOPSIS
+.BI "grab\-account " local\-user " " source " [" remote\-user ]
+.SH DESCRIPTION
+.B grab-account
+reconfigures sync-accounts to start synchronising a specified local
+user (which may not yet exist) from a specified remote system, and
+then invokes sync-accounts once to synchronise from that source.
+
+.B /etc/sync-accounts/createuser
+should contain a
+.B addhere
+line in the appropriate source section (ie, after
+.BR host " \fIsource\fP)."
+grab-account adds a
+.br
+.BR "  user" " \fIlocal\-user\fP [" remote= "\fIremote\-user\fP]"
+.br
+directive just before
+.B addhere
+and runs
+.BR sync-accounts " \fIsource\fP."
+.SH EXIT STATUS
+.TP
+.B 0
+All went well.
+.TP
+any other
+There were problems.
+.SH FILES
+.BR /etc/sync-accounts ;
+See also
+.BR sync-accounts (8).
+.SH ENVIRONMENT
+See
+.BR sync-accounts (8).
+.SH BUGS
+There is no locking of
+.B /etc/sync-accounts
+so do not invoke grab-account from a script, or more than once at a
+time by hand.  Do not edit /etc/sync-accounts by hand and also
+simultaneously run grab-account.
+
+The mechanism involving
+.B addhere
+is suboptimal.  This should be done with an include feature in
+sync-accounts, so that grab-account does not have to edit a
+configuration file that really belongs to the sysadmin.
+.SH AUTHOR
+.B grab-account
+and this manpage are part of the
+.B sync-accounts
+package which was written by Ian Jackson <ian@chiark.greenend.org.uk>.
+They are Copyright 1999-2000,2002 Ian Jackson
+<ian@davenant.greenend.org.uk>, and Copyright 2000-2001 nCipher
+Corporation Ltd.
+
+The sync-accounts package 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 3, or (at
+your option) any later version.
+
+This 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, consult the Free Software Foundation's
+website at www.fsf.org, or the GNU Project website at www.gnu.org.
+.SH SEE ALSO
+.BR sync-accounts "(8), "
+.BR sync-accounts "(5), "
+.BR passwd "(5)"
diff --git a/sync-accounts/sync-accounts b/sync-accounts/sync-accounts
new file mode 100755 (executable)
index 0000000..cef131c
--- /dev/null
@@ -0,0 +1,677 @@
+#!/usr/bin/perl
+# This is part of sync-accounts, a tool for synchronising UN*X password data.
+#
+# sync-accounts is
+#  Copyright 1999-2000,2002 Ian Jackson <ian@davenant.greenend.org.uk>
+#  Copyright 2000-2001 nCipher Corporation Ltd
+#
+#  sync-accounts 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 3, or (at
+#  your option) any later version.
+#
+#  sync-accounts 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 already have a copy of the GNU General Public License.
+#  If not, consult the Free Software Foundation's website at
+#  www.fsf.org, or the GNU Project website at www.gnu.org.
+#
+# $Id: sync-accounts,v 1.25 2007-09-21 21:21:15 ianmdlvl Exp $
+
+use POSIX;
+
+$configfile= '/etc/sync-accounts';
+$def_createuser= 'sync-accounts-createuser';
+$ch_homebase= '/home';
+$ch_defaultshell= '/bin/sh';
+$defaultgid= -1; # -1 => usergroups; -2 => nousergroups
+@groupglobs= [ '.*', 0 ];
+regroupglobs();
+
+$file{'passwd','std'}= 'passwd';
+$file{'shadow','std'}= 'shadow';
+$file{'group','std'}= 'group';
+
+$file{'passwd','bsd'}= 'master.passwd';
+$file{'shadow','bsd'}= 'shadow-non-existent';
+$file{'group','bsd'}= 'group';
+
+@fields_pw_std= qw(USER PW UID GID COMMENT HOME SHELL);
+@fields_pw_bsd= qw(USER PW UID GID CLASS CHANGE EXPIRE COMMENT HOME SHELL);
+fields_fmt('PW','std');
+fields('GR',qw(GROUP PW GID USERS));
+fields('SP',qw(USER PW DAYSCHGD DAYSFIX DAYSEXP DAYSWARN DAYSEXPDIS DAYSDISD RESERVED));
+# The name field had better always be field 0 !
+
+END {
+    foreach $x (@unlocks) {
+       ($fn, $style, $arg) = @$x;
+        &{ "unlock_$style" } ( $fn,$arg );
+    }
+}
+
+sub fields {
+    my ($pfx,@l) = @_;
+    my ($i, $v, $vn);
+    foreach $v (@l) { $vn= "${pfx}_$v"; $$vn = $i++; }
+    $vn= "${pfx}_fields"; $$vn= $i;
+}
+
+sub fields_fmt ($$) {
+    my ($pfx,$fmt) = @_;
+    my ($vn);
+    $vn= "fields_pw_$fmt";
+    die "unknown format $fmt\n" unless defined @$vn;
+    fields($pfx,@$vn);
+    $vn= "${pfx}_format";
+    $$vn= $fmt;
+}
+
+sub newentry {
+    my ($pfx,$name,@field_val_list) = @_;
+    my (@rv, $vn, $fn, $v, $i);
+    @rv= ();
+    $vn= "${pfx}_fields";
+    for ($i=0; $i<$$vn; $i++) { $rv[$i]= ''; }
+    die "@field_val_list ?" if @field_val_list % 2;
+    $rv[0] = $name;
+    while (@field_val_list) {
+       ($fn,$v,@field_val_list) = @field_val_list;
+       $vn= "${pfx}_$fn";
+#print STDERR ">$fn|$v|$vn|$$vn<\n";
+       $rv[$$vn]= $v;
+    }
+    return @rv;
+}
+
+$|=1;
+$cdays= int(time/86400);
+
+@envs_createuser= qw(USER UID GID COMMENT HOME SHELL);
+
+if (@ARGV == 1 && length $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'}) {
+    @na= map {
+       s/\%([0-9a-f][0-9a-f])/ pack("C", hex $1) /ge;
+       $_;
+    } split(/\:/, $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'});
+    delete $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'};
+    $ta= shift @na;
+    $ENV{"SYNC_ACCOUNTS_RUNVIA_LOCKEDGOT_$ta"} = $ARGV[0];
+    @ARGV= @na;
+}
+
+@orgargv= @ARGV;
+
+while ($ARGV[0] =~ m/^-/) {
+    $_= shift @ARGV;
+    last if m/^--$/;
+    if (m/^-C/) {
+       $configfile= $';
+    } elsif (m/^-n$/) {
+       $no_act= 1;
+       $display= 0;
+    } elsif (m/^-q$/) {
+       $no_act= 1;
+       $display= 1;
+    } else {
+       die "unknown option $_\n";
+    }
+}
+
+die "hosts must not be specified with -q\n" if @ARGV && $display;
+
+for $h (@ARGV) { $wanthost{$h}= 1; }    
+
+open CF,"< $configfile" or die "$configfile: $!";
+
+sub fetchfile (\%$) {
+    my ($ary_ref,$get_str) = @_;
+
+    undef %$ary_ref;
+    open G,"$get_str" or die "$get_str: $!";
+    while (<G>) {
+       chomp;
+       m/^([^:]+)\:/ or die "$ch_name: $get_str:$.: $_ ?\n";
+       $ary_ref->{$1}= [ split(/\:/,$_,-1) ];
+    }
+    close G; $? and die "$ch_name: $get_str: exit code $?\n";
+}
+
+sub lockstyle_ ($$) {
+    my ($fn,$lock) = @_;
+
+    die "$configfile:$.: locking mechanism for $fn not".
+       " defined (use lockpasswd/lockgroup)\n";
+}
+
+sub abslock (@) {
+    my ($fn,$lock) = @_;
+
+    $fn= "/etc/$fn";
+    $lock= "$fn$lock" unless $lock =~ m,^/,;
+    return ($fn,$lock);
+}
+
+sub lock_none ($$) { return (abslock(@_))[0]; }
+sub unlock_none ($$) { }
+
+sub lock_link ($$) {
+    my ($fn,$lock) = abslock(@_);
+    link $fn,$lock or die "cannot lock $fn by creating $lock: $!\n";
+    return $fn;
+}
+sub unlock_link ($$) {
+    my ($fn,$lock) = abslock(@_);
+    unlink $lock or warn "unable to unlock by removing $lock: $!\n";
+}
+
+sub lock_runvia ($$) {
+    my ($fn,$lock) = @_;
+    my ($evn);
+    $evn= "SYNC_ACCOUNTS_RUNVIA_LOCKEDGOT_$fn";
+    return $ENV{$evn} if exists $ENV{$evn};
+
+    @na= map {
+       s/\W/ sprintf("%%%02x", unpack("C", $&)) /ge;
+       $_;
+    } ($fn,@orgargv);
+    $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'}= join(":", @na);
+    $ENV{'EDITOR'}= $0;
+    delete $ENV{'VISUAL'};
+    exec $lock; die "cannot lock $fn by executing $lock: $!\n";
+}
+sub unlock_runvia ($$) { }
+
+sub fetchownfile (\@$$$$) {
+    my ($ary_ref,$fn_str,$nfields,$style,$lock_arg) = @_;
+    my ($fn_use, $record, $fn_emsg);
+    $fn_emsg= $fn_str;
+    if (!$no_act) {
+       $fn_use= &{ "lock_$style" } ($fn_str, $lock_arg);
+       push @unlocks, [ $fn_str, $style, $lock_arg ];
+       $savebackto{$fn_str}= $fn_use;
+    } else {
+       $fn_use= $fn_emsg= "/etc/".$file{$fn_str,$PW_format};
+    }
+    open O,"$fn_use" or die "$fn_use ($fn_str): $!";
+    while (<O>) {
+       chomp;
+       if (m/^\#/ || !m/\S/) {
+           $record= $_;
+       } else {
+           $record= [ split(/\:/,$_,-1) ];
+           die "$fn_emsg:$.:wrong number of fields:\`$_'\n"
+               unless @$record == $nfields;
+       }
+       push @$ary_ref, $record;
+    }
+    close O or die "$fn_use ($fn_str): $!";
+}
+
+sub diag ($) {
+    print "$diagstr: $_[0]\n" or die $!;
+}
+
+sub regroupglobs () {
+    $nogroups= (@groupglobs == 1 &&
+               $groupglobs[0]->[0] eq '.*' &&
+               !$groupglobs[0]->[1]);
+    $ggfunc= "sub wantsyncgroup {\n  \$_= \$_[0];\n  return\n";
+    for $g (@groupglobs) { $ggfunc.= "    m/^$g->[0]\$/ ? $g->[1] :\n"; }
+    $ggfunc.= "    die;\n};\n1;\n";
+#print STDERR "$ggfunc\n";
+    must_eval($ggfunc);
+}
+
+sub fetchown () {
+    if (!$own_fetchedpasswd) {
+#print STDERR ">$PW_fields<\n";
+       fetchownfile(@ownpasswd,'passwd',
+                    $PW_fields, $ch_lockstyle_passwd, $ch_lock_passwd);
+       $shadowfile= $file{'shadow',$PW_format};
+       if (stat("/etc/$shadowfile")) {
+           $own_haveshadow= 1;
+           $own_fetchedshadow= 1;
+           fetchownfile(@ownshadow,'shadow',$SP_fields,'none','');
+       } elsif ($! == &ENOENT) {
+           $own_haveshadow= 0;
+       } else {
+           die "unable to check for /etc/$shadowfile: $!\n";
+       }
+       $own_fetchedpasswd= 1;
+    }
+    if (!$own_fetchedgroup) {
+       fetchownfile(@owngroup,'group',$GR_fields,
+                    $ch_lockstyle_group, $ch_lock_group);
+       $own_fetchedgroup= 1;
+    }  
+#print STDERR "fetchown() -> $#ownpasswd $#owngroup\n";
+}
+
+sub checkuid ($$) {
+    my ($useuid,$foruser) = @_;
+    for $e (@ownpasswd) {
+       next unless ref $e;
+       if ($e->[$PW_USER] ne $foruser && $e->[$PW_UID] == $useuid) {
+           diag("uid clash with $e->[$PW_USER] (uid $e->[$PW_UID])");
+           return 0;
+       }
+    }
+    return 1;
+}
+
+sub must_eval ($) {
+    eval $_[0] or die "$_[0] // $@";
+}
+
+sub copyfield ($$$$) {
+    my ($file,$entry,$field,$value) = @_;
+    must_eval("\$ary_ref= \\\@own$file; 1;");
+#print STDERR "copyfield($file,$entry,$field,$value)\n";
+    for $e (@$ary_ref) {
+#print STDERR "copyfield($file,$entry,$field,$value) $e->[0] $e->[field] ".join(':',@$e)."\n";
+       next unless $e->[0] eq $entry;
+       next if $e->[$field] eq $value;
+       $e->[$field]= $value;
+       must_eval("\$modified$file= 1; 1;");
+    }
+}
+
+sub fetchpasswd () {
+    return if $ch_fetchedpasswd;
+    die "$configfile:$.: getpasswd not specified for host $ch_name\n"
+       unless length $ch_getpasswd;
+    undef %remshadow;
+    fetchfile(%rempasswd,"$ch_getpasswd |");
+    if (length $ch_getshadow) {
+       fetchfile(%remshadow,"$ch_getshadow |");
+       for $k (keys %rempasswd) {
+           $rempasswd{$k}->[$REM_PW]= 'xx' unless length $rempasswd{$k}->[$REM_PW];
+       }
+       for $k (keys %remshadow) {
+           next unless exists $rempasswd{$k};
+           $rempasswd{$k}->[$REM_PW]= $remshadow{$k}->[$SP_PW];
+       }
+    }
+    $ch_fetchedpasswd= 1;
+}
+
+sub fetchgroup () {
+    return if $ch_fetchedgroup;
+    die "$configfile:$.: getgroup not specified for host $ch_name\n"
+       unless length $ch_getgroup;
+    fetchfile(%remgroup,"$ch_getgroup |");
+    $ch_fetchedgroup= 1;
+}
+
+sub syncusergroup ($$) {
+    my ($lu,$luid) = @_;
+
+    return 1 if $defaultgid != -1;
+#print STDERR "syncusergroup($lu,$luid)\n";
+    $ugfound=0;
+    
+    for $e (@owngroup) {
+       next unless ref $e;
+       $samename= $e->[$GR_GROUP] eq $lu;
+       $sameid= $e->[$GR_GID] eq $luid;
+       next unless $samename || $sameid;
+       if (!$samename || !$sameid) {
+           diag("local group $e->[$GR_GROUP] ($e->[$GR_GID]) mismatch vs.".
+                " local user $lu ($luid)");
+           return 0;
+       }
+       if ($ugfound) {
+           diag("per-user group $lu ($luid) duplicated");
+           return 0;
+       }
+       $ugfound=1;
+    }
+
+    return 1 if $ugfound;
+
+    if (!length $opt_createuser) {
+       diag("account creation not enabled, not creating per-user group");
+       return 0;
+    }
+    push @owngroup, [ newentry('GR', $lu,
+                              'PW', 'x',
+                              'GID', $luid) ];
+    $modifiedgroup= 1;
+    return 1;
+}
+
+sub hosthead ($) {
+    my ($th) = @_;
+    return if $hostheaddone eq $th;
+    print "\n\n" or die $! if length $hostheaddone;
+    print "==== $th ====\n" or die $!;
+    $hostheaddone= $th;
+}
+
+sub syncuser ($$) {
+    my ($lu,$ru) = @_;
+    my ($vn);
+
+#print STDERR "syncuser($lu,$ru)\n";
+    return if $doneuser{$lu}++;
+    next unless $ch_doinghost;
+    return if !length $ru;
+
+    fetchown();
+
+    if ($display) {
+       for $e (@ownpasswd) {
+           next unless ref $e && $e->[$PW_USER] eq $lu;
+           hosthead("from $ch_name");
+           print ($lu eq $ru ? " $lu" : " $lu($ru)") or die $!;
+           print "<DUPLICATE>" if $displaydone{$lu}++;
+       }
+       return;
+    }
+    
+    $diagstr= "user $lu from $ch_name!$ru";
+
+#print STDERR "syncuser($lu,$ru) doing\n";
+    fetchpasswd();
+
+    if (!$rempasswd{$ru}) { diag("no remote entry"); return; }
+    if (length $ch_getshadow && exists $remshadow{$ru} &&
+       length $remshadow{$ru}->[$SP_DAYSDISD]) {
+       diag("remote account disabled in shadow");
+       return;
+    }
+
+    if (!grep(ref $_ && $_->[$PW_USER] eq $lu, @ownpasswd)) {
+       if (!length $opt_createuser) { diag("account creation not enabled"); return; }
+       if ($no_act) { diag("-n specified; not creating account"); return; }
+
+       if ($opt_sameuid) {
+           $useuid= $rempasswd{$ru}->[$REM_UID];
+           $usegid= $rempasswd{$ru}->[$REM_GID];
+       } else {
+           die "nousergroups specified, cannot create users\n" if $defaultgid==-2;
+           length $ch_uidmin or die "no uidmin specified, cannot create users\n";
+           length $ch_uidmax or die "no uidmax specified, cannot create users\n";
+           $ch_uidmin<$ch_uidmax or die "uidmin>=uidmax, cannot create users\n";
+       
+           $useuid= $ch_uidmin;
+           for $e ($defaultgid==-1 ? (@ownpasswd, @owngroup) : (@ownpasswd)) {
+               next unless ref $e;
+               $tuid= $e->[$PW_UID]; next if $tuid<$useuid || $tuid>$ch_uidmax;
+               if ($tuid==$ch_uidmax) {
+                   diag("uid (or gid?) $ch_uidmax used, cannot create users");
+                   return;
+               }
+               $useuid= $tuid+1;
+           }
+           $usegid= $defaultgid==-1 ? $useuid : $defaultgid;
+       }
+       
+       @newpwent= newentry('PW', $lu,
+                           'PW', 'x',
+                           'UID', $useuid,
+                           'GID', $usegid,
+                           'COMMENT', $rempasswd{$ru}->[$REM_COMMENT],
+                           'HOME', "$ch_homebase/$lu",
+                           'SHELL', $ch_defaultshell);
+       
+       defined($c= open CU,"-|") or die $!;
+       if (!$c) {
+           @unlocks= ();
+           defined($c2= open STDIN,"-|") or die $!;
+           if (!$c2) {
+               print STDOUT join(':',@newpwent),"\n" or die $!;
+               exit 0;
+           }
+           for ($i=0; $i<@envs_createuser; $i++) {
+               $vn= "PW_$envs_createuser[$i]";
+#print STDERR ">$i|$vn|$$vn|$newpwent[$$vn]<\n";
+               $ENV{"SYNCUSER_CREATE_$envs_createuser[$i]"}= $newpwent[$$vn];
+           }
+           exec $opt_createuser; die "$configfile:$.: ($lu): $opt_createuser: $!\n";
+       }
+       $newpwent= <CU>;
+       close CU; $? and die "$configfile:$.: ($lu): $opt_createuser: code $?\n";
+       chomp $newpwent;
+       if (length $newpwent) {
+           if ($newpwent !~ m/\:/) { diag("creation script demurred"); return; }
+           @newpwent= split(/\:/,$newpwent,-1);
+       }
+       die "$opt_createuser: bad result: \`".join(':',@newpwent)."\'\n"
+           if @newpwent != $PW_fields or $newpwent[$PW_USER] ne $lu;
+       checkuid($newpwent[$PW_UID],$lu) or return;
+       if ($own_haveshadow) {
+           push @ownshadow, [ newentry('SP', $lu,
+                                       'PW', 'x',
+                                       'DAYSCHGD', $cdays,
+                                       'DAYSFIX', 0,
+                                       'DAYSEXP', 99999,
+                                       'DAYSEXPDIS', 7) ];
+           $modifiedshadow= 1;
+       }
+       syncusergroup($lu,$newpwent[$PW_UID]) or return;
+       push @ownpasswd,[ @newpwent ];
+       $modifiedpasswd= 1;
+    }
+
+    for $e (@ownpasswd) {
+       next unless ref $e && $e->[$PW_USER] eq $lu;
+       syncusergroup($lu,$e->[$PW_UID]) or return;
+    }
+
+    $ruid= $rempasswd{$ru}->[$REM_UID];
+    $rgid= $rempasswd{$ru}->[$REM_GID];
+    if ($opt_sameuid && checkuid($ruid,$lu)) {
+       for $e (@ownpasswd) {
+           next unless ref $e && $e->[$PW_USER] eq $lu;
+           $luid= $e->[$PW_UID]; $lgid= $e->[$PW_GID];
+           die "$diagstr: local uid $luid, remote uid $ruid\n" if $luid ne $ruid;
+           die "$diagstr: local gid $lgid, remote gid $rgid\n" if $lgid ne $rgid;
+       }
+    }
+
+#print STDERR "syncuser($lu,$ru) exists $own_haveshadow\n";
+    if ($own_haveshadow && grep(ref $_ && $_->[$PW_USER] eq $lu, @ownshadow)) {
+#print STDERR "syncuser($lu,$ru) shadow $rempasswd{$ru}->[$REM_PW]\n";
+       copyfield('shadow',$lu,$SP_PW, $rempasswd{$ru}->[$REM_PW]);
+    } else {
+#print STDERR "syncuser($lu,$ru) passwd $rempasswd{$ru}->[$REM_PW]\n";
+       copyfield('passwd',$lu,$PW_PW, $rempasswd{$ru}->[$REM_PW]);
+    }
+    copyfield('passwd',$lu,$PW_COMMENT, $rempasswd{$ru}->[$REM_COMMENT]);
+
+    $newsh= $rempasswd{$ru}->[$REM_SHELL];
+    $oksh= $checkedshell{$newsh};
+    if (!length $oksh) { $checkedshell{$newsh}= $oksh= (-x $newsh) ? 1 : 0; }
+    copyfield('passwd',$lu,$PW_SHELL, $newsh) if $oksh;
+
+    if (!$nogroups) {
+       for $e (@owngroup) {
+           next unless ref $e;
+           $tgroup= $e->[$GR_GROUP];
+#print STDERR "syncuser($lu,$ru) group $tgroup\n";
+           next unless &wantsyncgroup($tgroup);
+#print STDERR "syncuser($lu,$ru) group $tgroup yes\n";
+           fetchgroup();
+           if (!exists $remgroup{$tgroup}) {
+               diag("group $tgroup: not on remote host");
+               next;
+           }
+           $inremote= grep($_ eq $ru, split(/\,/,$remgroup{$tgroup}->[$GR_USERS]));
+           $cusers= $e->[$GR_USERS]; $inlocal= grep($_ eq $lu, split(/\,/,$cusers));
+           if ($inremote && !$inlocal) {
+               $cusers.= ',' if length $cusers;
+               $cusers.= $lu;
+           } elsif ($inlocal && !$inremote) {
+               $cusers= join(',', grep($_ ne $lu, split(/\,/, $cusers)));
+           } else {
+               next;
+           }
+           $e->[$GR_USERS]= $cusers;
+           $modifiedgroup= 1;
+       }
+    }
+}
+
+sub banner () {
+    return if $bannerdone;
+    print "\n" or die $!; system 'date'; $? and die $?;
+    $bannerdone= 1;
+}
+
+sub finish () {
+    my ($record);
+
+    for $h (keys %wanthost) {
+       die "host $h not in config file\n" if $wanthost{$h};
+    }
+
+    if ($display) {
+#print STDERR "\n\nfinish display=$display pw=$pw\n\n";
+       for $e (@ownpasswd) {
+           next unless ref $e;
+           $tu= $e->[$PW_USER];
+           $tuid= $e->[$PW_UID];
+           next if $displaydone{$tu};
+           $tpw= $e->[$PW_PW];
+#print STDERR ">$tu|$tpw<\n";
+           for $e2 (@ownshadow) {
+               next unless ref $e2 && $e2->[$SP_USER] eq $tu;
+               $tpw= $e2->[$SP_PW]; last;
+           }
+           $tpw= length($tpw)>=13 ? 1 : length($tpw) ? -1 : 0;
+           $head= ($tpw == 1 ? "unsynched" :
+                   $tpw == 0 ? "unsynched, passwordless" :
+                   "unsynched, no-logins").
+                   ($tuid < 100 ? " system account" : " normal user");
+           $unsynch_type{$head} .= " $e->[$PW_USER]";
+       }
+       foreach $head (sort keys %unsynch_type) {
+           hosthead($head);
+           print $unsynch_type{$head} or die $!;
+       }
+       print "\n\n" or die $!;
+       exit 0;
+    }
+    
+    umask 077;
+    for $file (qw(passwd shadow group)) {
+       $realfile= $file{$file,$PW_format};
+       must_eval("\$modified= \$modified$file; \$data_ref= \\\@own$file;".
+                 " \$fetched= \$own_fetched$file; 1;");
+       next if !$modified;
+       die $file unless $fetched;
+       banner();
+       if ($no_act) {
+           $newfileinst= "/etc/$realfile";
+           $newfile= "$realfile.new";
+       } else {
+           $newfileinst= $savebackto{$file};
+           $newfile= "$newfileinst.new";
+       }
+       open NF,"> $newfile" or die "$newfile: $!";
+       for $e (@$data_ref) {
+           $record= ref $e ? join(':',@$e) : $e;
+           print NF $record,"\n" or die $!;
+       }
+       close NF or die $!;
+       system 'diff','-U0','--label',$realfile,$newfileinst,
+                            '--label',"$realfile.new",$newfile;
+       $?==256 or die $?;
+       if (!$no_act) {
+           (@stats= stat $newfileinst) or die "$file: $!";
+           chown $stats[4],$stats[5], $newfile or die $!;
+           chmod $stats[2] & 07777, $newfile or die $!;
+           rename $newfile, $newfileinst or die $!;
+       }
+    }
+    exit 0;
+}
+
+while (<CF>) {
+    chomp;
+    s/^\s*//; s/\s*$//;
+    next if m/^\#/ || !m/\S/;
+    finish() if m/^end$/;
+    if (m/^host\s+(\S+)$/) {
+       $ch_name= $1;
+       $ch_getpasswd= $ch_getgroup= $ch_getshadow= '';
+       $ch_fetchedpasswd= $ch_fetchedgroup;
+       if (!@ARGV) {
+           $ch_doinghost= 1;
+       } elsif (exists $wanthost{$ch_name}) {
+           $wanthost{$ch_name}= 0;
+           $ch_doinghost= 1;
+       } else {
+           $ch_doinghost= 0;
+       }
+       fields_fmt('REM','std');
+    } elsif (m/^(getpasswd|getshadow|getgroup)\s+(.*\S)$/) {
+       must_eval("\$ch_$1= \$2; 1;");
+    } elsif (m/^(local|remote)format\s+(\w+)$/) {
+       fields_fmt($1 eq 'local' ? 'PW' :
+                  $1 eq 'remote' ? 'REM' : die,
+                  $2);
+    } elsif (m/^lock(passwd|group)\s+(runvia|link)\s+(\S+)$/) {
+       must_eval("\$ch_lock_$1= \$3; \$ch_lockstyle_$1= \$2; 1;");
+    } elsif (m/^lock(passwd|group)\s+(none)$/) {
+       must_eval("\$ch_lockstyle_$1= \$2; 1;");
+    } elsif (m,^(homebase|defaultshell)\s+(/\S+)$,) {
+       must_eval("\$ch_$1= \$2; 1;");
+    } elsif (m/^(uidmin|uidmax)\s+(\d+)$/ && $2>0) {
+       must_eval("\$ch_$1= \$2; 1;");
+    } elsif (m/^createuser$/) {
+       $opt_createuser= $def_createuser;
+    } elsif (m/^nocreateuser$/) {
+       $opt_createuser= '';
+    } elsif (m/^createuser\s+(\S+)$/) {
+       $opt_createuser= $1;
+    } elsif (m/^logfile\s+(.*\S)$/) {
+       if (!$no_act) {
+           open STDOUT,">> $1" or die "$1: $!"; $|=1;
+           $!=0; system 'date'; $? and die "date: $! $?\n";
+       } elsif (!$display) {
+           print "would log to $1\n" or die $!;
+       }
+    } elsif (m/^(no|)(sameuid)$/) {
+       must_eval("\$opt_$2= ".($1 eq 'no' ? 0 : 1)."; 1;");
+    } elsif (m/^usergroups$/) {
+       $defaultgid= -1;
+    } elsif (m/^nousergroups$/) {
+       $defaultgid= -2;
+    } elsif (m/^defaultgid\s+(\d+)$/) {
+       $defaultgid= $1;
+    } elsif (m/^(no|)group\s+([-+.0-9a-zA-Z*?]+)$/) {
+       $yes= $1 eq 'no' ? 0 : 1;
+       $_= $2;
+       @groupglobs=() if $_ eq '*';
+       s/[-+._]/\\$&/g;
+       s/\*/\.\*/g;
+       s/\?/\./g;
+       unshift @groupglobs, [ $_, $yes ];
+       regroupglobs();
+    } elsif (m/^user\s+(\S+)$/) {
+       syncuser($1,$1);
+    } elsif (m/^user\s+(\S+)\s+remote\=(\S+)$/) {
+       syncuser($1,$2);
+    } elsif (m/^nouser\s+(\S+)$/) {
+       syncuser($1,'');
+    } elsif (m/^users\s+(\d+)\-(\d+)$/) {
+       $tmin= $1; $tmax= $2; $except= $3;
+       fetchpasswd();
+       for $k (keys %rempasswd) {
+           $tuid= $rempasswd{$k}->[2];
+           next if $tuid<$1 or $tuid>$2;
+           syncuser($k,$k);
+       }
+    } elsif (m/^addhere$/) {
+    } else {
+       die "$configfile:$.: unknown directive\n";
+    }
+}
+
+die "$configfile:$.: missing \`end', or read error\n";
diff --git a/sync-accounts/sync-accounts-createuser b/sync-accounts/sync-accounts-createuser
new file mode 100755 (executable)
index 0000000..a8f7904
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+# $Id: sync-accounts-createuser,v 1.6 2007-09-21 21:21:15 ianmdlvl Exp $
+#
+# Copyright 1999-2002 Ian Jackson <ian@davenant.greenend.org.uk>
+#
+#  This 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 3, or (at your option) any later
+#  version.
+#
+#  This 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 already have a copy of the GNU General Public License.
+#  If not, consult the Free Software Foundation's website at
+#  www.fsf.org, or the GNU Project website at www.gnu.org.
+
+set -e
+un=$SYNCUSER_CREATE_USER
+ui=$SYNCUSER_CREATE_UID
+gi=$SYNCUSER_CREATE_GID
+ho=$SYNCUSER_CREATE_HOME
+test -d $ho || mkdir $ho
+chgrp $gi $ho
+chown $ui $ho
+chmod 2755 $ho
diff --git a/sync-accounts/sync-accounts-createuser.8 b/sync-accounts/sync-accounts-createuser.8
new file mode 100644 (file)
index 0000000..ffd897d
--- /dev/null
@@ -0,0 +1,101 @@
+.\" Hey, Emacs!  This is an -*- nroff -*- source file.
+.TH SYNC\-ACCOUNTS\-CREATEUSER 8 "14th July 2002" "Greenend" "chiark utilities"
+.SH NAME
+sync\-accounts\-createuser \- helper/hook program for sync\-accounts
+.SH SYNOPSIS
+.BI SYNCUSER_CREATE_ var = "value\fP... \fI" sync\-accounts\-createuser
+.SH DESCRIPTION
+.B sync-accounts-createuser
+is invoked by
+.B sync-accounts
+when sync-accounts is creating a local account.
+
+It must perform all of the tasks involved with local account creation
+except for the actual changes to the password, shadow and group
+databases.
+
+At the very minimum, it must create the new account's home directory
+(with appropriate permissions).  The supplied sync-accounts-createuser
+script does exactly that.
+
+It may also suggest to sync-accounts modifications to the new
+account's passwd entry.
+.SH INVOCATION
+When sync-accounts-createuser is invoked, the passwd and group entries
+will not yet have been set up, so it may not rely on them.
+sync-accounts-createuser will not be supplied with any arguments.
+However, the following environment variables will be set, giving
+details about the account to be created:
+.br
+.B " " SYNCUSER_CREATE_USER
+.br
+.B " " SYNCUSER_CREATE_UID
+.br
+.B " " SYNCUSER_CREATE_GID
+.br
+.B " " SYNCUSER_CREATE_COMMENT
+.br
+.B " " SYNCUSER_CREATE_HOME
+.br
+.B " " SYNCUSER_CREATE_SHELL
+.SH RESULTS
+sync-accounts-createuser should usually produce no output.
+
+It can inhibit the creation of the user by outputting a single line
+not containing a colon; in this case, a diagnostic message will be
+written to sync-accounts's logfile, and the user will be skipped.
+
+Alternatively, it may write out an alternative password file entry, in
+which case sync-accounts will use the supplied data for the local
+passwd file instead of that from the remote host.
+The line should be in
+Sys-V passwd file format (regardless of
+.B localformat
+or
+.B remoteformat
+settings).  The username field should be taken from
+.BR SYNCUSER_CREATE_USER ,
+and the password field should be
+.BR x .
+.SH EXIT STATUS
+.TP
+.B 0
+All went well, or we wrote a line without a colon to say
+that the account should not be created.
+.TP
+any other
+There were serious problems and sync-accounts should bomb out
+immediately.
+.SH FILES
+None.
+.SH ENVIRONMENT
+See above.
+.SH BUGS
+The supplied sync-accounts-createuser does not check that it
+was not supplied with any arguments; nor does it check that the
+.B SYNCUSER_CREATE_*
+variables are set, or have sensible values.
+.SH AUTHOR
+.B sync-accounts-createuser
+and this manpage were written by Ian Jackson
+<ian@chiark.greenend.org.uk>.  They are Copyright 1999-2002 Ian
+Jackson <ian@davenant.greenend.org.uk>.  This manpage forms part of
+the sync-accounts package.
+
+sync-accounts-createuser and the sync-accounts package are 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 3, or (at your option) any later version.
+
+This 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, consult the Free Software Foundation's
+website at www.fsf.org, or the GNU Project website at www.gnu.org.
+.SH SEE ALSO
+.BR sync-accounts "(8), "
+.BR sync-accounts "(5), "
+.BR passwd "(5)"
diff --git a/sync-accounts/sync-accounts.5 b/sync-accounts/sync-accounts.5
new file mode 100644 (file)
index 0000000..c5d198b
--- /dev/null
@@ -0,0 +1,396 @@
+.\" Hey, Emacs!  This is an -*- nroff -*- source file.
+.TH SYNC\-ACCOUNTS 5 "15th July 2002" "Greenend" "chiark utilities"
+.SH NAME
+/etc/sync\-accounts \- configuration file for sync\-accounts
+.SH DESCRIPTION
+.B /etc/sync\-accounts
+contains the default configuration of the
+.BR sync-accounts (8)
+account synchronisation tool.
+
+The configuration file specifies how to access and update the local
+password and group databases, where sync-accounts should log.
+
+It also specifies the list of (remote) sources for account
+information, and which accounts and details should be copied from each
+source to the local system.
+.SH OVERALL SYNTAX AND SEMANTICS
+The configuration file is parsed as a series of lines.  First, leading
+and trailing whitespace on each line is removed, and then empty lines,
+or lines starting with
+.BR # ,
+are removed.
+
+Each line is parsed as a directive.  The order of directives is
+significant; some directives set up information which later
+directives rely on.
+
+The configuration file must contain an
+.B end
+directive; anything after that point is ignored.
+.SH GLOBAL DIRECTIVES
+These directives may appear only at the start of the file (before any
+other directives), and each directive must appear only once;
+otherwise, sync-accounts my behave oddly.
+.TP
+.BR lockpasswd | lockgroup " \fImethod\fP [\fIdetails \fP...]"
+Specifies how the passwd and group files should be read and/or locked.
+See
+.B LOCKING METHOD DIRECTIVES
+below.
+.TP
+.BI "logfile " filename
+Append log messages to
+.I filename
+instead of stdout.
+Errors still go to stderr.
+.TP
+.BR localformat " " bsd | std
+Specifies the local password file is in the relevant format:
+.B std
+is the standard V7 password file (with a SysV-style
+/etc/shadow if /etc/shadow exists).
+.B bsd
+is the BSD4.4 master.passwd format, and should be used only with
+.BR "lockpasswd runvia vipw" .
+The default is
+.BR std .
+.SH LOCKING METHOD DIRECTIVES
+One
+.B lockgroup
+and one
+.B lockpasswd
+directive must be present, in the global directives at the start of
+the config file.
+
+The choice of the appropriate directives can be difficult without
+special knowledge of the local system.  In general, it is best to use
+.B lockpasswd runvia vipw
+where this is available, as if this works avoids having to know the
+names of the lockfiles.
+
+GNU systems (including GNU/Linux and Debian GNU/BSD) typically lock
+the group file separately and supply
+.BR vigr ,
+in which case you should use
+.BR "lockgroup vigr" .
+
+Most systems other than GNU do not lock the group file at all (or
+assume that all programs which modify the group file will lock the
+passwd file), in which case
+.B lockgroup none
+is appropriate.
+
+If vigr or vipw is not available or is known to be broken (eg, because
+it does not lock properly), then use
+.BR link .
+.TP
+.BR lockpasswd | lockgroup " " runvia " \fIprogram\fP
+sync-accounts will reinvoke itself using
+.IR program ,
+which must behave like
+.B vipw
+or
+.BR vigr .
+sync-accounts will set the
+.B EDITOR
+environment variable to the path it was invoked with (Perl's
+.BR $0 )
+and put some information for its own use into
+.B SYNC_ACCOUNTS_*
+environment variables (which will also allow sync-accounts to tell
+that it has already been reinvoked via
+.I program
+and should not do so again).
+
+If both
+.BI "lockpasswd runvia " vipw
+and
+.BI "lockgroup runvia " vigr
+are specified, then it must be possible and safe for the EDITOR
+run by
+.I vipw
+to invoke
+.IR vigr ,
+as this is what sync-accounts will do.
+.TP
+.BR lockpasswd | lockgroup " " link " \fIsuffix\fP|\fIfilename\fP
+sync-accounts will attempt to lock the passwd or group file by making
+a hardlink from the real file to the specified filename.
+If
+.IR suffix | filename
+starts with a
+.B /
+it is interpreted as a filename; otherwise it is interpreted as
+a suffix, to be appended to the real database filename.
+.TP
+.BR lockpasswd | lockgroup " " none
+sync-accounts will not attempt to lock the passwd or group files at
+all.
+
+.B lockgroup none
+is appropriate on systems where there is no separate locking for the
+group file (either because there is no proper support for automatic
+editing of the group file, or because you're expected to lock the
+password file), although in the absence of
+.B vigr
+it's inevitable that simultaneous changes to the group file made by
+both the human sysadmin and by sync-accounts will cause problems.
+
+.B lockpasswd none
+is very dangerous and should not normally be used.  It will cause data
+loss if any other tool for changing password data is used - eg,
+.BR passwd (1).
+.SH PER-SOURCE DIRECTIVES
+Within each source's section, all of the per-source directives must
+appear before any account-selection directives; otherwise
+sync-accounts may behave oddly.  If a per-source directive is
+repeated, the last setting takes effect.
+.TP
+.BI "host " source
+Starts a source's section.  Usually each source will correspond
+exactly to one host which is acting as a source of account data.
+The
+.B host
+directive resets the per-source parameters to the defaults.
+.I source
+need not be the source host's official name in any sense and is used
+only for identification.  Any
+.I source
+must be named in only one
+.B host
+directive, or sync-accounts may behave oddly.
+.TP
+.BR getpasswd | getgroup | getshadow " \fIcommand\fP..."
+sync-accounts always fetches account data from sources by running specified
+commands on the local host; it does not contain any network protocols,
+itself.
+
+.I command
+is fed to
+.BR "sh -c"
+and might typically contain something like
+.br
+.B "    ssh syncacct@remote.host cat /etc/passwd"
+.br
+where the user syncacct on remote.host is in group shadow, or
+.br
+.B "    cat /var/local/sync-accounts/remote.host/passwd"
+where the file named is copied across using cron.
+
+.B getpasswd
+must be specified if user data is to be transferred;
+.B getgroup
+must be specified if group data is to be transferred.
+
+.B getshadow
+should be specified iff getpasswd is specified but the data from
+getpasswd does not contain actual password information, and should
+emit data in Sys-V shadow password format.
+.TP
+.BR remoteformat " " std | bsd
+Specifies the format of the output of getpasswd.
+.B std
+is standard V7 passwd file format (optionally augmented by the use of
+a shadow file fetched with getshadow).
+.B bsd
+is the BSD4.4 master.passwd format (and getshadow should not normally
+be used with
+.BR "remoteformat bsd" ).
+The default is
+.BR std .
+.SH SYNCHRONISATION SETTINGS
+The following directives affect the way that account data is copied.
+They may be freely mixed with other directives, and repeated.  The
+setting in effect is the one set by the last relevant settings
+directive before any particular account-selection directive.
+.TP
+.BR uidmin | uidmax " \fivalue\fP"
+When an account is to be created locally, a uid/gid will be chosen
+which is one higher than the highest currently in use, except that ids
+below uidmin or above uidmax are ignored and will never be used.
+There is no default.
+.TP
+.BI "homebase " homebase
+When an account is to be created locally, its home directory will be
+.IB homebase / username
+where
+.I username
+is the name of the account.  The default is
+.BR /home .
+.TP
+.RB [ no ] sameuid
+Specifies whether uids are supposed to match.  With
+.BR sameuid ,
+it is an error for the uid or gid of a synchronised local account not
+to match the corresponding remote account, and new local accounts will
+get the remote accounts' ids.
+The default is
+.BR nosameuid .  
+.TP
+.BR usergroups " | " nousergroups " | " defaultgid " \fIgid\fP"
+Specifies whether local accounts are supposed to have
+corresponding groups, or all be part of a particular group.  The
+default is
+.BR usergroups .
+
+With
+.BR usergroups ,
+when a new account is created, the
+corresponding per-user group will be created as well, and
+per-user groups are created for existing accounts if necessary
+(if account creation is enabled).  If the gid or group name for
+a per-user group is already taken for a different group name or
+gid this will be logged, and processing of that account will be
+inhibited, but it is not a fatal error.
+
+With
+.BR defaultgid ,
+newly-created accounts will be made a part of that group,
+and the groups of existing accounts will be left alone.
+
+With
+.BR nousergroups ,
+no new accounts can be created, and existing accounts' groups will be
+left alone.
+.TP
+.BR createuser " [\fIcommand\fP] | " nocreateuser
+Specifies whether accounts found on the remote host should be created
+if necessary, and what command to run to do the the rest of the
+account setup (eg, creation of home directory, etc.).  The default is
+.BR nocreateuser .
+
+If
+.B createuser
+is specified without a command then
+.B sync-accounts-createuser
+is used; the supplied sync-accounts-createuser program is a reasonable
+minimal implementation.
+
+With
+.BR createuser ,
+either sameuid, or both uidmin and uidmax, must be specified, if
+accounts are actually to be created.
+
+The command is passed to
+.BR "sh -c" .
+See
+.BR sync-accounts-createuser (8)
+for details of
+.IR command 's
+environment and functionality.
+.TP
+.BR group | nogroup " \fIglob-pattern\fP"
+.B group
+specifies that the membership of the local groups specified should be
+adjusted adjusted whenever account data for any user is copied, so
+that the account will be a member of the affected group locally iff
+the source account it is a member of the same group on the source
+host.
+
+The most recently-encountered glob-pattern for a particular group
+takes effect.  The default is
+.BR "nogroups *" .
+
+The glob patterns may contain only alphanumerics, the two glob
+metacharacters
+.BR "* ?"
+and four punctuation characters
+.BR "- + . _" ;
+\fB\\\fP-quoting and character sets and ranges are not supported.
+.TP
+.BI "defaultshell " pathname
+Local accounts' shells will, when an account is synchronised, be set
+to the remote account's shell if the same file exists locally and is
+executable.  Otherwise, this value will be used.  The
+default is
+.BR /bin/sh .
+.SH ACCOUNT SELECTION
+These directives specify that the selected accounts are to be
+synchronised: that is, the local account data will be unconditionally
+overwritten (according to the synchronisation settings) with data from
+the current source (according to the most recent
+.B host
+directive).
+
+Any particular local username will only be synchronised once; the
+source and settings for first account selection directive which
+selects that local username will be used.
+
+When an account is synchronised, the account password, comment field,
+and shell will be copied unconditionally.  If
+.B sameuid
+is in effect specified the uid will be checked (or copied, for new
+accounts).
+.TP
+.BR user " \fIusername\fP [" remote "=\fIremoteusername\fP]"
+Specifies that account data should be copied for local user
+.I username
+from the remote account
+.I remoteusername
+(or
+.I username
+if
+.I remoteusername
+is not specified).
+.TP
+.RI "\fBusers\fP " ruidmin - ruidmax
+Specifies that all remote users whose remote uid is in the given range
+are to be synchronised to corresponding user accounts.  (Note that the
+remote uid will only be copied if
+.B sameuid
+is in effect.)
+.TP
+.BI "nouser " username
+Specifies that data for
+.I username is not to be copied, even
+if subsequent user or users directives suggest that it should be.
+.TP
+.B addhere
+This directive has no effect on sync-accounts.  However, it is used as
+a placeholder by grab-account: new accounts for creation are inserted
+just before addhere.  See
+.BR grab-account (8).
+.SH FINAL DIRECTIVE
+.TP
+.B end
+must appear in the configuration file, usually at the end of the file.
+Nothing after it will be read.
+.SH BUGS
+The advice about the correct
+.B lockpasswd
+and
+.B lockgroup
+directives is probably out of date or flatly wrong.
+.SH AUTHOR
+.B sync-accounts
+and this manpage are part of the
+.B sync-accounts
+package which was written by Ian Jackson <ian@chiark.greenend.org.uk>.
+They are Copyright 1999-2000,2002 Ian Jackson
+<ian@davenant.greenend.org.uk>, and Copyright 2000-2001 nCipher
+Corporation Ltd.
+
+The sync-accounts package 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 3, or (at
+your option) any later version.
+
+This 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, consult the Free Software Foundation's
+website at www.fsf.org, or the GNU Project website at www.gnu.org.
+.SH SEE ALSO
+.BR sync-accounts "(8), "
+.BR grab-account "(8), "
+.BR sync-accounts-createuser "(8), "
+.BR passwd "(5), "
+.BR group "(5), "
+.BR shadow "(5), "
+.BR master.passwd "(5), "
+.BR vipw "(8), "
+.BR vigr "(8)"
diff --git a/sync-accounts/sync-accounts.8 b/sync-accounts/sync-accounts.8
new file mode 100644 (file)
index 0000000..5e2ca29
--- /dev/null
@@ -0,0 +1,155 @@
+.\" Hey, Emacs!  This is an -*- nroff -*- source file.
+.TH SYNC\-ACCOUNTS 8 "14th July 2002" "Greenend" "chiark utilities"
+.SH NAME
+sync\-accounts \- synchronise accounts and passwords
+.SH SYNOPSIS
+.BR sync\-accounts " [\fIoptions\fP] [\fIsource\fP ...]"
+.SH DESCRIPTION
+.B sync-accounts
+is a tool for copying account information into the local system's
+password and group databases, or equivalent, from other systems.  It
+can be used to slave individual accounts, whole systems, or various
+partial combinations.
+
+By default, when invoked, sync-accounts reads is configuration file
+and updates all of the local details it is configured to synchronise,
+from all relevant sources.
+
+If one or more \fIsource\fPs are named as command-line arguments, only
+information from those sources is installed locally.
+
+See
+.BR sync-accounts(5)
+for detailed information about sync-accounts's behaviour and
+configuration.
+.SH OPTIONS
+.TP
+.BI \-C config\-file
+Reads
+.I config-file
+instead of
+.BR /etc/sync-accounts .
+.TP
+.BR \-q
+Instead of updating local information, sync-accounts displays a
+summary of which accounts are synchronised or not, and from where.
+.TP
+.BR \-n
+Causes sync-accounts not to actually install the new information in
+the local password and group databases.  Instead, updated versions are
+written to the files
+.B passwd
+and
+.B group
+in the current directory.  With
+.B \-n
+new accounts are not created at all.  The system databases are not
+locked.
+.SH SECURITY
+sync-accounts is not resistant to malicious data in the local
+password and group databases, or its configuration file or command
+line arguments.
+
+Malicious data in source information will not be able to take control
+of sync-accounts, but will be copied to the local databases if
+sync-accounts is configured to do so.
+
+To update the local databases, sync-accounts must be run as root.
+For \-q and \-n sync-accounts still needs to be able to successfuly
+invoke the commands specified in the configuration for getpasswd and
+getgroup.
+.SH EXIT STATUS
+.TP
+.B 0
+All went well and there were no warnings.
+.TP
+any other
+There were problems.  The local databases may or may not have been
+updated.
+.SH FILES
+.TP
+.B /etc/sync-accounts
+Default configuration file.  (Override with
+.BR -C .)
+.TP
+.B sync-accounts-createuser
+Default command invoked by sync-accounts to create local users.
+.TP
+.B /home
+Default location for created users' home directories.
+.TP
+.B /bin/sh
+Default shell for created users.
+.TP
+.BR /etc/passwd ", " /etc/group ", " /etc/shadow ", " /etc/master.passwd
+Local account databases, depending on configuration.
+.TP
+.BR /etc/shadow-non-existent
+Must not exist.
+.SH ENVIRONMENT
+.TP
+.BR EDITOR ", " VISUAL
+Manipulated by sync-\accounts when it is reinvoking itself via vipw or
+vigr, according to
+.B lockpasswd runvia
+or
+.BR "lockgroup runvia" .
+.TP
+.BR SYNC_ACCOUNTS_*
+Used by sync-accounts for its own purposes.  Do not set these
+variables.
+.LP
+Setting variables used by
+.BR vipw (8)
+and
+.BR vigr (8),
+apart from
+.BR EDITOR " and/or"  VISUAL
+will affect the operation of sync-accounts.  
+Avoid messing with these if possible.
+.LP
+.B PATH
+is used to find subprograms such as
+.BR sync-accounts-createuser " and " vipw / vigr .
+.SH BUGS
+Using sync-accounts does not give particularly prompt propagation of
+changed account information.
+
+There is no simple mechanism for automatically getting the right
+configuration details for accessing the local system's password and
+group databases.
+
+All the systems sharing account information using sync-accounts need
+to be using compatible encrypted-password schemes.
+.SH AUTHOR
+.B sync-accounts
+and this manpage are part of the
+.B sync-accounts
+package which was written by Ian Jackson <ian@chiark.greenend.org.uk>.
+They are Copyright 1999-2000,2002 Ian Jackson
+<ian@davenant.greenend.org.uk>, and Copyright 2000-2001 nCipher
+Corporation Ltd.
+
+The sync-accounts package 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 3, or (at
+your option) any later version.
+
+This 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, consult the Free Software Foundation's
+website at www.fsf.org, or the GNU Project website at www.gnu.org.
+.SH SEE ALSO
+.BR sync-accounts "(5), "
+.BR grab-account "(8), "
+.BR sync-accounts-createuser "(8), "
+.BR passwd "(5), "
+.BR group "(5), "
+.BR shadow "(5), "
+.BR master.passwd "(5), "
+.BR vipw "(8), "
+.BR vigr "(8)"
diff --git a/sync-accounts/sync-accounts.example-bsd b/sync-accounts/sync-accounts.example-bsd
new file mode 100644 (file)
index 0000000..e943183
--- /dev/null
@@ -0,0 +1,33 @@
+# This is an example /etc/sync-accounts for BSD.
+
+lockpasswd runvia vipw
+lockgroup none
+localformat bsd
+logfile /var/log/sync-accounts
+
+uidmin 500
+uidmax 29999
+homebase /home
+defaultshell /usr/local/bin/bash
+
+host fowey
+getpasswd rsh fowey -l syncacct cat /etc/passwd
+getshadow rsh fowey -l syncacct cat /etc/shadow
+getgroup rsh fowey -l syncacct cat /etc/group
+createuser sync-accounts-createuser
+sameuid
+group devel
+
+nousergroups
+user devel
+usergroups
+
+users 500-29999
+
+nogroup *
+nocreateuser
+nousergroups
+nosameuid
+#user root
+
+end
diff --git a/sync-accounts/sync-accounts.example-linux b/sync-accounts/sync-accounts.example-linux
new file mode 100644 (file)
index 0000000..73d015f
--- /dev/null
@@ -0,0 +1,76 @@
+# This is an example /etc/sync-accounts for Linux.
+
+lockpasswd runvia vipw
+lockgroup runvia vigr
+logfile /var/log/sync-accounts
+
+uidmin 1000
+uidmax 29999
+homebase /u2
+defaultshell /bin/bash
+
+host sfere
+getpasswd ssh sfere -l ian cat /etc/passwd
+createuser
+user richardk remote=richard
+addhere
+
+#host snoopy
+#getpasswd ssh snoopy -l ian cat /etc/passwd
+#getshadow ssh snoopy -l ian really cat /etc/shadow
+#createuser
+#user christi
+#addhere
+
+host khem
+getpasswd rsh khem -l ian cat /etc/passwd
+createuser
+user andrewm
+addhere
+
+host chiark
+getpasswd cat /var/lib/sync-accounts/chiark/passwd
+getshadow cat /var/lib/sync-accounts/chiark/shadow
+getgroup cat /var/lib/sync-accounts/chiark/group
+createuser sync-accounts-createuser-chiark2davenant
+sameuid
+user richard
+user ian
+
+user ijackson
+user owend
+user damerell
+user jonr
+user duncanm
+user siona
+user dans
+user marisal
+user vivienh
+user davidr
+user fanf
+user wednsday
+user emmaburt
+user eleanorb
+user stevee
+user diziet
+user matthewv
+user mattheww
+user sgtatham
+user jdamery
+user twomack
+user aldabra
+user janetmck
+user johns
+user andreww
+user sbleas
+user clareb
+user chrisj
+user beckyc
+addhere
+
+nogroup *
+nocreateuser
+nousergroups
+user root
+
+end