--- /dev/null
+
+ 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>.
+
--- /dev/null
+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
--- /dev/null
+# 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
--- /dev/null
+# 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;
--- /dev/null
+#!/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
--- /dev/null
+#!/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; }
--- /dev/null
+#!/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
--- /dev/null
+/var/spool/news/chiark
+!/var/spool/news
+!/tmp
+end
--- /dev/null
+include fsys.pt0
+include fsys.pt1
+end
--- /dev/null
+/ dump
+/var dump
+/u dump
+end
--- /dev/null
+/u2 dump
+/usr dump
+/var/spool/news/chiark cpio
+end
--- /dev/null
+#
+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;
--- /dev/null
+# 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=
--- /dev/null
+filesystems all
+next b0
+end
--- /dev/null
+filesystems all
+next c0
+end
--- /dev/null
+filesystems pt1
+next c0
+end
--- /dev/null
+filesystems all
+next d0
+end
--- /dev/null
+filesystems pt1
+next d0
+end
--- /dev/null
+filesystems all
+next a0
+end
--- /dev/null
+filesystems pt1
+next a0
+end
--- /dev/null
+incremental
+end
--- /dev/null
+incremental
+end
--- /dev/null
+T 45 "in 1 minute"
+T 15 "in 15 seconds"
--- /dev/null
+#!/bin/sh
+set -e
+chvt 11
--- /dev/null
+#!sfere:/opt/bigdisc
+!davenant:/export/mirror
+davenant:/export/mirror/work
+!davenant:/export/mp3
+!kadath:/tmp
+#!elmyra:/mnt/fat/data
+end
--- /dev/null
+include ifsys.prefixes
+include ifsys.pt0
+include ifsys.pt1
+include ifsys.pt2
+end
--- /dev/null
+include ifsys.prefixes
+include ifsys.pt0
+end
--- /dev/null
+include ifsys.prefixes
+include ifsys.pt1
+end
--- /dev/null
+include ifsys.prefixes
+include ifsys.pt2
+end
--- /dev/null
+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
--- /dev/null
+/usr/src dump davenant
+
+/var dump davenant
+/u dump davenant
+
+/ dump davenant
+
+/export/mirror/work cpio davenant
+
+/usr dump davenant
+/boot dump davenant
+
+end
--- /dev/null
+/ dump kadath
+
+/usr dump kadath
+/var dump kadath
+/home dump kadath
+
+/var dump khem
+/home dump khem
+/ dump khem
+
+/usr dump khem
+
+end
--- /dev/null
+/ dump xenophobe
+
+/ dump
+
+/dos/c cpio
+/dos/d cpio
+
+end
--- /dev/null
+# 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;
--- /dev/null
+incremental
+end
--- /dev/null
+incremental
+end
--- /dev/null
+filesystems pt0
+next s1
+end
--- /dev/null
+filesystems pt1
+next s2
+end
--- /dev/null
+filesystems pt2
+next t0
+end
--- /dev/null
+filesystems pt0
+next t1
+end
--- /dev/null
+filesystems pt1
+next t2
+end
--- /dev/null
+filesystems pt2
+next u0
+end
--- /dev/null
+filesystems pt0
+next u1
+end
--- /dev/null
+filesystems pt1
+next u2
+end
--- /dev/null
+filesystems pt2
+next v0
+end
--- /dev/null
+filesystems pt0
+next v1
+end
--- /dev/null
+filesystems pt1
+next v2
+end
--- /dev/null
+filesystems pt2
+next s0
+end
--- /dev/null
+T 45 "in 1 minute"
+T 15 "in 15 seconds"
--- /dev/null
+#!/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;
--- /dev/null
+#!/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;
--- /dev/null
+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.
--- /dev/null
+#!/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;
--- /dev/null
+#!/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
--- /dev/null
+#!/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'
--- /dev/null
+.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.
--- /dev/null
+.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.
--- /dev/null
+.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.
--- /dev/null
+.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.
--- /dev/null
+.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.
--- /dev/null
+.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.
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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'
--- /dev/null
+# 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
+}
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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);
+}
--- /dev/null
+# 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)
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+.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)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+.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)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#!/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";
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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*/
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+.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.
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#!/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}
--- /dev/null
+.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)
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/**/
+
+#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);
+ }
+}
--- /dev/null
+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
--- /dev/null
+/*
+ * 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, ¤t_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));
+}
--- /dev/null
+.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)
--- /dev/null
+/*
+ * 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");
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+.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)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+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:
--- /dev/null
+/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
--- /dev/null
+/usr/bin/xbatmon-simple /usr/bin/xacpi-simple
--- /dev/null
+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.
--- /dev/null
+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.
--- /dev/null
+#!/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
--- /dev/null
+#!/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;
--- /dev/null
+# 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
--- /dev/null
+#!/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" '{}' ';'
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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 $
--- /dev/null
+#!/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 $
--- /dev/null
+#!/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
--- /dev/null
+#!/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;
+}
--- /dev/null
+.\" 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.
--- /dev/null
+#!/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 $
--- /dev/null
+#!/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 "$? $!";
+ }
+}
--- /dev/null
+#!/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 $!;
--- /dev/null
+.\" 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.
--- /dev/null
+#!/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: $!");
--- /dev/null
+#!/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
--- /dev/null
+#!/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 $!;
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+#!/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
--- /dev/null
+.\" 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)"
--- /dev/null
+#!/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";
--- /dev/null
+#!/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
--- /dev/null
+.\" 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)"
--- /dev/null
+.\" 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)"
--- /dev/null
+.\" 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)"
--- /dev/null
+# 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
--- /dev/null
+# 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