From: Ian Jackson Date: Sun, 10 Jun 2012 20:40:32 +0000 (+0100) Subject: chiark-utils (4.2.0) unstable; urgency=low X-Git-Tag: debian/4.3.0~11^2 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-utils.git;a=commitdiff_plain;h=dcb3df3fda85c612a887127d5357806238c72f0b 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) # imported from the archive --- dcb3df3fda85c612a887127d5357806238c72f0b diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..4432540 --- /dev/null +++ b/COPYING @@ -0,0 +1,676 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..e67e14d --- /dev/null +++ b/TODO @@ -0,0 +1,17 @@ +BACKUP +====== + +would be nice someday +--------------------- +increms after fulls +replace loaded with idformat +read/writebuffer setuid --mlock +whatsthis no cloneandhack +increm no cloneandhack +whatsthis listing for zafio and dump archives +configuration files autogenerator + + +SCRIPTS +======= +manpage for gnucap2gnuplot diff --git a/backup/Makefile b/backup/Makefile new file mode 100644 index 0000000..9ca10ba --- /dev/null +++ b/backup/Makefile @@ -0,0 +1,70 @@ +# Makefile +# simple make settings +# +# This file is part of chiark backup, a system for backing up GNU/Linux and +# other UN*X-compatible machines, as used on chiark.greenend.org.uk. +# +# chiark backup is: +# Copyright (C) 1997-1998,2000-2001,2007 +# Ian Jackson +# Copyright (C) 1999 Peter Maydell +# +# 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 +# Copyright (C) 1999 Peter Maydell +# +# 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= ''; + $pcstr= ''; + $rstr= ''; + } elsif ($tf =~ m#^(/\S*)\s+(\w+)([,=0-9a-z]*)\s+(\w+)$#) { + # Line of form '[/device:]/file/system dumptype[,options] prefix' + # (used for remote backups) + $atf= $1; + $tm= $2; + $dopts= $3; + $prefix= $4; + $pcstr= "$prefix:"; + defined($prefix{$prefix}) or die "prefix $prefix in $tf ?\n"; + $rstr= $prefix{$prefix}.' '; + } else { + die "fsys $tf ?"; + } + + $fsidstr= $pcstr.$atf; + $fsidstr =~ s/[,+]/+$&/g; + $fsidstr =~ s#/#,#g; + $fsidfile= "/var/lib/chiark-backup/incstamp,$fsidstr"; + + $dev = $atf =~ s,^(.*)\:,, ? $1 : ''; + + if (!length $pcstr) { + stat $atf or die "stat $atf: $!"; + -d _ or die "not a dir: $atf"; + } + + undef %dopt; + foreach $dopt (split /\,/,$dopts) { + if (grep { $dopt eq $_ } qw(gz noinc)) { + $dopt{$dopt}= 'y'; + } elsif (grep { $dopt eq $_ } qw(snap)) { + $dopt{$dopt}= $dopt; + } elsif ($dopt =~ m/\=/ && grep { $` eq $_ } qw(gz snap)) { + $dopt{$`}= $'; + } elsif (length $dopt) { + die "unknown option $dopt (in $dopts $tf)"; + } + } + + my ($gzo); + foreach $gzo (qw(gz gzi)) { + if ($dopt{$gzo} eq 'y') { + $$gzo= '1'; + } elsif ($dopt{$gzo} =~ m/^\d$/) { + $$gzo= $dopt{$gzo}; + } elsif (defined $dopt{$gzo}) { + die "$tf bad $gzo"; + } else { + $$gzo= ''; + } + } + + if (length $dopt{'snap'}) { + length $dev or die "$pcstr:$atf no device but needed for snap"; + } +} + +sub execute ($) { + pboth(" $_[0]\n"); + system $_[0]; $? and die "$_[0] $?"; +} + +sub prepfsys () { + $dev_print= $dev; + $atf_print= $atf; + + if (length $dopt{'snap'}) { + + system('snap-drop'); $? and die $?; + + $snapscripts= '/etc/chiark-backup/snap'; + $snapbase= "$rstr $snapscripts/$dopt{'snap'}"; + $snapargs= "/var/lib/chiark-backup"; + + $snapsnap= "$snapbase snap $snapargs $dev $atf"; + $snapdrop= "$snapbase drop $snapargs"; + + open SD, ">snap-drop.new" or die $!; + print SD $snapdrop,"\n" or die $!; + close SD or die $!; + rename "snap-drop.new","snap-drop" or die $!; + + execute($snapsnap); + + $dev_nosnap= $dev; + $atf_nosnap= $atf; + $dev= "/var/lib/chiark-backup/snap-device"; + $atf= "/var/lib/chiark-backup/snap-mount"; + } +} + +sub finfsys () { + if (length $dopt{'snap'}) { + system('snap-drop'); $? and die $?; + } +} + +sub openlog () { + unlink 'log'; + $u= umask(007); + open LOG, ">log" or die $!; + umask $u; + select(LOG); $|=1; select(STDOUT); +} + +$SIG{'__DIE__'}= 'killprocesses'; + +1; diff --git a/backup/bringup b/backup/bringup new file mode 100755 index 0000000..b88a720 --- /dev/null +++ b/backup/bringup @@ -0,0 +1,32 @@ +#!/bin/sh +# bringup +# script for going back to defaultrunlevel +# +# This file is part of chiark backup, a system for backing up GNU/Linux and +# other UN*X-compatible machines, as used on chiark.greenend.org.uk. +# +# chiark backup is: +# Copyright (C) 1997-1998,2000-2001,2007 +# Ian Jackson +# Copyright (C) 1999 Peter Maydell +# +# This is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3, or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +# Very simple: extract the default runlevel from /etc/inittab +# and change to it with telinit. + +runlevel=`sed -ne '/^id:/ s/.*:\([0-9]\):.*/\1/p' /etc/inittab` +telinit $runlevel + +test ! -f /etc/chiark-backup/bringup-hook diff --git a/backup/checkallused b/backup/checkallused new file mode 100755 index 0000000..c0079d0 --- /dev/null +++ b/backup/checkallused @@ -0,0 +1,182 @@ +#!/usr/bin/perl +# checkallused +# check that the configuration is sane and backs up everything it should +# +# This file is part of chiark backup, a system for backing up GNU/Linux and +# other UN*X-compatible machines, as used on chiark.greenend.org.uk. +# +# chiark backup is: +# Copyright (C) 1997-1998,2000-2001,2007 +# Ian Jackson +# Copyright (C) 1999 Peter Maydell +# +# 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= ); +close X or die $!; + +while (!defined $tapedone{$tape}) { + open X,"$etc/tape.$tape" or die "$tape $!"; + $fsg=''; $next=''; + for (;;) { + $_= 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 $!; + $_= ; m/^Filesystem/ or die "$cmd => $_ ?"; + $prefix= length($pfx) ? $pfx : ''; + $pcstr= length($pfx) ? "$pfx:" : ''; + print "mount points: $prefix:"; + while () { + 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 (;;) { + $_= or die; chomp; s/\s*$//; + last if m/^end$/; + next unless m/^\S/; + next if m/^\#/; + if (s/^\?//) { + print "non-permanent filesystem expected not to be dumped: $_\n"; + if (defined($mounted{$_})) { + delete $mounted{$_}; + } + } elsif (s/^\!//) { + &e("expected not to be dumped, but not a mount point: $_") + unless defined($mounted{$_}); + print "filesystem expected not to be dumped: $_\n"; + delete $mounted{$_}; + } else { + &e("non-filesystem expected to be dumped is mounted: $_ on $mounted{$_}") + if defined($mounted{$_}); + $mounted{$_}= 'expected-diffs'; + print "non-filesystem expected to be dumped: $_\n"; + } +} + +for $fs (sort keys %backed) { length($mounted{$fs}) || &e("dumped ($backed{$fs}), not a mount point: $fs"); } +for $fs (sort keys %incrd) { length($mounted{$fs}) || &e("increm'd ($incrd{$fs}), not a mount point: $fs"); } +for $fs (sort keys %mounted) { length($backed{$fs}) || &e("mount point ($mounted{$fs}), not dumped: $fs"); } +for $fs (sort keys %mounted) { length($incrd{$fs}) || &e("mount point ($mounted{$fs}), not increm'd: $fs"); } + +$emsg.= "configuration ok\n" unless $e; +print STDERR $emsg; +exit($e); + +sub e { $emsg.="** @_\n"; $e=1; } diff --git a/backup/driver b/backup/driver new file mode 100755 index 0000000..5dab3eb --- /dev/null +++ b/backup/driver @@ -0,0 +1,60 @@ +#!/bin/sh +# driver +# entry point for inittab (or perhaps cron) to run the backups. +# +# This file is part of chiark backup, a system for backing up GNU/Linux and +# other UN*X-compatible machines, as used on chiark.greenend.org.uk. +# +# chiark backup is: +# Copyright (C) 1997-1998,2000-2001,2007 +# Ian Jackson +# Copyright (C) 1999 Peter Maydell +# +# 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 < (lvm) +# -l (remountrocp) +#lvm_lvtools_opts='-A n' +#lvm_lvcreate_opts= +#lvm_lvcreate_args= diff --git a/backup/examples/chiark/tape.a0 b/backup/examples/chiark/tape.a0 new file mode 100644 index 0000000..219e728 --- /dev/null +++ b/backup/examples/chiark/tape.a0 @@ -0,0 +1,3 @@ +filesystems all +next b0 +end diff --git a/backup/examples/chiark/tape.b0 b/backup/examples/chiark/tape.b0 new file mode 100644 index 0000000..9b0d81f --- /dev/null +++ b/backup/examples/chiark/tape.b0 @@ -0,0 +1,3 @@ +filesystems all +next c0 +end diff --git a/backup/examples/chiark/tape.b1 b/backup/examples/chiark/tape.b1 new file mode 100644 index 0000000..0eab3f9 --- /dev/null +++ b/backup/examples/chiark/tape.b1 @@ -0,0 +1,3 @@ +filesystems pt1 +next c0 +end diff --git a/backup/examples/chiark/tape.c0 b/backup/examples/chiark/tape.c0 new file mode 100644 index 0000000..cb23647 --- /dev/null +++ b/backup/examples/chiark/tape.c0 @@ -0,0 +1,3 @@ +filesystems all +next d0 +end diff --git a/backup/examples/chiark/tape.c1 b/backup/examples/chiark/tape.c1 new file mode 100644 index 0000000..44f2b50 --- /dev/null +++ b/backup/examples/chiark/tape.c1 @@ -0,0 +1,3 @@ +filesystems pt1 +next d0 +end diff --git a/backup/examples/chiark/tape.d0 b/backup/examples/chiark/tape.d0 new file mode 100644 index 0000000..6fbdc69 --- /dev/null +++ b/backup/examples/chiark/tape.d0 @@ -0,0 +1,3 @@ +filesystems all +next a0 +end diff --git a/backup/examples/chiark/tape.d1 b/backup/examples/chiark/tape.d1 new file mode 100644 index 0000000..4856464 --- /dev/null +++ b/backup/examples/chiark/tape.d1 @@ -0,0 +1,3 @@ +filesystems pt1 +next a0 +end diff --git a/backup/examples/chiark/tape.ka b/backup/examples/chiark/tape.ka new file mode 100644 index 0000000..cad1fa7 --- /dev/null +++ b/backup/examples/chiark/tape.ka @@ -0,0 +1,2 @@ +incremental +end diff --git a/backup/examples/chiark/tape.kb b/backup/examples/chiark/tape.kb new file mode 100644 index 0000000..cad1fa7 --- /dev/null +++ b/backup/examples/chiark/tape.kb @@ -0,0 +1,2 @@ +incremental +end diff --git a/backup/examples/chiark/warnings.now b/backup/examples/chiark/warnings.now new file mode 100755 index 0000000..e69de29 diff --git a/backup/examples/chiark/warnings.soon b/backup/examples/chiark/warnings.soon new file mode 100755 index 0000000..14d7bd2 --- /dev/null +++ b/backup/examples/chiark/warnings.soon @@ -0,0 +1,2 @@ +T 45 "in 1 minute" +T 15 "in 15 seconds" diff --git a/backup/examples/relativity/bringup-hook b/backup/examples/relativity/bringup-hook new file mode 100755 index 0000000..2d57080 --- /dev/null +++ b/backup/examples/relativity/bringup-hook @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +chvt 11 diff --git a/backup/examples/relativity/expected-diffs b/backup/examples/relativity/expected-diffs new file mode 100644 index 0000000..5609903 --- /dev/null +++ b/backup/examples/relativity/expected-diffs @@ -0,0 +1,7 @@ +#!sfere:/opt/bigdisc +!davenant:/export/mirror +davenant:/export/mirror/work +!davenant:/export/mp3 +!kadath:/tmp +#!elmyra:/mnt/fat/data +end diff --git a/backup/examples/relativity/fsys.all b/backup/examples/relativity/fsys.all new file mode 100644 index 0000000..cf611fb --- /dev/null +++ b/backup/examples/relativity/fsys.all @@ -0,0 +1,5 @@ +include ifsys.prefixes +include ifsys.pt0 +include ifsys.pt1 +include ifsys.pt2 +end diff --git a/backup/examples/relativity/fsys.pt0 b/backup/examples/relativity/fsys.pt0 new file mode 100644 index 0000000..3cd925c --- /dev/null +++ b/backup/examples/relativity/fsys.pt0 @@ -0,0 +1,3 @@ +include ifsys.prefixes +include ifsys.pt0 +end diff --git a/backup/examples/relativity/fsys.pt1 b/backup/examples/relativity/fsys.pt1 new file mode 100644 index 0000000..a9210e7 --- /dev/null +++ b/backup/examples/relativity/fsys.pt1 @@ -0,0 +1,3 @@ +include ifsys.prefixes +include ifsys.pt1 +end diff --git a/backup/examples/relativity/fsys.pt2 b/backup/examples/relativity/fsys.pt2 new file mode 100644 index 0000000..2e786f9 --- /dev/null +++ b/backup/examples/relativity/fsys.pt2 @@ -0,0 +1,3 @@ +include ifsys.prefixes +include ifsys.pt2 +end diff --git a/backup/examples/relativity/ifsys.prefixes b/backup/examples/relativity/ifsys.prefixes new file mode 100644 index 0000000..4e317aa --- /dev/null +++ b/backup/examples/relativity/ifsys.prefixes @@ -0,0 +1,9 @@ +prefix davenant ssh -o 'BatchMode yes' -c blowfish -o 'Compression yes' davenant 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH' +prefix xenophobe ssh -o 'BatchMode yes' -c blowfish -o 'Compression no' xenophobe 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH' + +prefix khem ssh -o 'BatchMode yes' -c blowfish -o 'Compression yes' -o 'CompressionLevel 1' khem 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH' + +prefix kadath ssh -o 'BatchMode yes' -c blowfish -o 'Compression yes' -o 'CompressionLevel 1' kadath -l ian 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:$PATH really' +prefix-df kadath /bin/df -t noprocfs,nfs + +end diff --git a/backup/examples/relativity/ifsys.pt0 b/backup/examples/relativity/ifsys.pt0 new file mode 100644 index 0000000..2305e5f --- /dev/null +++ b/backup/examples/relativity/ifsys.pt0 @@ -0,0 +1,13 @@ +/usr/src dump davenant + +/var dump davenant +/u dump davenant + +/ dump davenant + +/export/mirror/work cpio davenant + +/usr dump davenant +/boot dump davenant + +end diff --git a/backup/examples/relativity/ifsys.pt1 b/backup/examples/relativity/ifsys.pt1 new file mode 100644 index 0000000..7ec91b9 --- /dev/null +++ b/backup/examples/relativity/ifsys.pt1 @@ -0,0 +1,13 @@ +/ dump kadath + +/usr dump kadath +/var dump kadath +/home dump kadath + +/var dump khem +/home dump khem +/ dump khem + +/usr dump khem + +end diff --git a/backup/examples/relativity/ifsys.pt2 b/backup/examples/relativity/ifsys.pt2 new file mode 100644 index 0000000..ac447bb --- /dev/null +++ b/backup/examples/relativity/ifsys.pt2 @@ -0,0 +1,8 @@ +/ dump xenophobe + +/ dump + +/dos/c cpio +/dos/d cpio + +end diff --git a/backup/examples/relativity/settings.pl b/backup/examples/relativity/settings.pl new file mode 100644 index 0000000..9aa7eb4 --- /dev/null +++ b/backup/examples/relativity/settings.pl @@ -0,0 +1,20 @@ +# configuration, for putting in /etc/chiark-backup + +chdir '/var/lib/chiark-backup' or die $!; +push(@INC,'/usr/share/chiark-backup'); +$ENV{'PATH'} =~ s,^/usr/share/chiark-backup,,; +$ENV{'PATH'}= '/usr/share/chiark-backup:'.$ENV{'PATH'}; + +# This sets both blocksizes to 512b. Note that both must be the +# same if using the zftape floppy tape driver, since that requires +# blocks to be the right size, but dd with the obs=10k option +# doesn't pad the final block to the blocksize... + +$blocksize= 1; +$blocksizebytes= 512*$blocksize; +$softblocksizekb= 1; +$softblocksizebytes= 1024*$softblocksizekb; +$tapename= 'st0'; +$tape= "/dev/$tapename"; +$ntape= "/dev/n$tapename"; +1; diff --git a/backup/examples/relativity/tape.ks b/backup/examples/relativity/tape.ks new file mode 100644 index 0000000..cad1fa7 --- /dev/null +++ b/backup/examples/relativity/tape.ks @@ -0,0 +1,2 @@ +incremental +end diff --git a/backup/examples/relativity/tape.kt b/backup/examples/relativity/tape.kt new file mode 100644 index 0000000..cad1fa7 --- /dev/null +++ b/backup/examples/relativity/tape.kt @@ -0,0 +1,2 @@ +incremental +end diff --git a/backup/examples/relativity/tape.s0 b/backup/examples/relativity/tape.s0 new file mode 100644 index 0000000..a2a30dd --- /dev/null +++ b/backup/examples/relativity/tape.s0 @@ -0,0 +1,3 @@ +filesystems pt0 +next s1 +end diff --git a/backup/examples/relativity/tape.s1 b/backup/examples/relativity/tape.s1 new file mode 100644 index 0000000..7d333f5 --- /dev/null +++ b/backup/examples/relativity/tape.s1 @@ -0,0 +1,3 @@ +filesystems pt1 +next s2 +end diff --git a/backup/examples/relativity/tape.s2 b/backup/examples/relativity/tape.s2 new file mode 100644 index 0000000..81499eb --- /dev/null +++ b/backup/examples/relativity/tape.s2 @@ -0,0 +1,3 @@ +filesystems pt2 +next t0 +end diff --git a/backup/examples/relativity/tape.t0 b/backup/examples/relativity/tape.t0 new file mode 100644 index 0000000..3da59fe --- /dev/null +++ b/backup/examples/relativity/tape.t0 @@ -0,0 +1,3 @@ +filesystems pt0 +next t1 +end diff --git a/backup/examples/relativity/tape.t1 b/backup/examples/relativity/tape.t1 new file mode 100644 index 0000000..2e5b4f5 --- /dev/null +++ b/backup/examples/relativity/tape.t1 @@ -0,0 +1,3 @@ +filesystems pt1 +next t2 +end diff --git a/backup/examples/relativity/tape.t2 b/backup/examples/relativity/tape.t2 new file mode 100644 index 0000000..627dbd9 --- /dev/null +++ b/backup/examples/relativity/tape.t2 @@ -0,0 +1,3 @@ +filesystems pt2 +next u0 +end diff --git a/backup/examples/relativity/tape.u0 b/backup/examples/relativity/tape.u0 new file mode 100644 index 0000000..0c47152 --- /dev/null +++ b/backup/examples/relativity/tape.u0 @@ -0,0 +1,3 @@ +filesystems pt0 +next u1 +end diff --git a/backup/examples/relativity/tape.u1 b/backup/examples/relativity/tape.u1 new file mode 100644 index 0000000..f570303 --- /dev/null +++ b/backup/examples/relativity/tape.u1 @@ -0,0 +1,3 @@ +filesystems pt1 +next u2 +end diff --git a/backup/examples/relativity/tape.u2 b/backup/examples/relativity/tape.u2 new file mode 100644 index 0000000..44dcec4 --- /dev/null +++ b/backup/examples/relativity/tape.u2 @@ -0,0 +1,3 @@ +filesystems pt2 +next v0 +end diff --git a/backup/examples/relativity/tape.v0 b/backup/examples/relativity/tape.v0 new file mode 100644 index 0000000..b9f3ccb --- /dev/null +++ b/backup/examples/relativity/tape.v0 @@ -0,0 +1,3 @@ +filesystems pt0 +next v1 +end diff --git a/backup/examples/relativity/tape.v1 b/backup/examples/relativity/tape.v1 new file mode 100644 index 0000000..bfbc0e0 --- /dev/null +++ b/backup/examples/relativity/tape.v1 @@ -0,0 +1,3 @@ +filesystems pt1 +next v2 +end diff --git a/backup/examples/relativity/tape.v2 b/backup/examples/relativity/tape.v2 new file mode 100644 index 0000000..b85219a --- /dev/null +++ b/backup/examples/relativity/tape.v2 @@ -0,0 +1,3 @@ +filesystems pt2 +next s0 +end diff --git a/backup/examples/relativity/warnings.now b/backup/examples/relativity/warnings.now new file mode 100755 index 0000000..e69de29 diff --git a/backup/examples/relativity/warnings.soon b/backup/examples/relativity/warnings.soon new file mode 100755 index 0000000..14d7bd2 --- /dev/null +++ b/backup/examples/relativity/warnings.soon @@ -0,0 +1,2 @@ +T 45 "in 1 minute" +T 15 "in 15 seconds" diff --git a/backup/full b/backup/full new file mode 100755 index 0000000..61b6266 --- /dev/null +++ b/backup/full @@ -0,0 +1,337 @@ +#!/usr/bin/perl +# full +# Main backup script - does a full dump or execs increm. Do NOT run directly! +# +# This file is part of chiark backup, a system for backing up GNU/Linux and +# other UN*X-compatible machines, as used on chiark.greenend.org.uk. +# +# chiark backup is: +# Copyright (C) 1997-1998,2000-2001,2007 +# Ian Jackson +# Copyright (C) 1999 Peter Maydell +# +# 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= ); +$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= ); + 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. +# files be links to tape. 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 (;;) { + $_= 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= '&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 '&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

>this-md5sums + # dump <$dumpin | tee p [| gzip] | writebuffer | dd >/dev/null + + startprocess '>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= ); $orgsum =~ s/\ +\-?$//; + $orgsum =~ m/^[0-9a-fA-F]{32}$/i or die "orgsum \`$orgsum' ?"; + $cmd= "$nasty dd if=$ntape ibs=$blocksizebytes"; + $cmd .= " | $nasty readbuffer"; + $cmd .= " | $nice gzip -vd" if $gz; + $cmd .= " | $nice md5sum"; + pboth(" $cmd\n"); + chomp($csum= `$cmd`); + $csum =~ s/\ +\-?$//; + $orgsum eq $csum or die "MISMATCH $tf $csum $orgsum\n"; + print "checksum ok $csum\t$tf\n" or die $!; + print LOG "checksum ok $csum\t$tf\n" or die $!; +} +printdate(); +runsystem("mt -f $tape rewind"); + +setstatus "FAILED during cleanup"; + +$summary= ''; +foreach $tfs (@tapefilesizes) { + $summary .= sprintf " %10d blocks for %s\n", $tfs->[0], $tfs->[1] +} +$summary .= + sprintf " %10d blocks total (of %d bytes) plus TAPEID and headers\n", + $totalrecords, $blocksizebytes; + +pboth("size-summary:\n"); +pboth($summary); + +open SS, ">size-summary..new" or die $!; +print SS $summary or die $!; +close SS or die $!; +rename 'size-summary..new',"size-summary.$fsys" or die $!; + +# Write to some status files to indicate what the backup system +# ought to do when next invoked. +# reset incremental backup count to 1. +open IAN,">increm-advance.new" or die $!; +print IAN "1\n" or die $!; +close IAN or die $!; + +# Next full backup is whatever the next link in the tape description +# file says it ought to be. +open TN,">next-full.new" or die $!; +print TN "$next\n" or die $!; +close TN or die $!; + +unlink 'last-tape','next-full'; +# We are the last tape to have been backed up +rename 'TAPEID','last-tape' or die $!; +rename 'this-md5sums',"md5sums.$fsys" or die $!; +rename 'log',"log.$fsys" or die $!; +rename 'next-full.new',"next-full" or die $!; +rename 'increm-advance.new',"increm-advance" or die $!; + +print "$doing completed.\nNext dump tape is $next.\n" or die $!; + +setstatus "Successful: $tapedesc $fsys, next $next"; +exit 0; diff --git a/backup/increm b/backup/increm new file mode 100755 index 0000000..c9d52ca --- /dev/null +++ b/backup/increm @@ -0,0 +1,151 @@ +#!/usr/bin/perl +# increm +# Do an incremental backup; ONLY for invocation by full ! +# +# This file is part of chiark backup, a system for backing up GNU/Linux and +# other UN*X-compatible machines, as used on chiark.greenend.org.uk. +# +# chiark backup is: +# Copyright (C) 1997-1998,2000-2001,2007 +# Ian Jackson +# Copyright (C) 1999 Peter Maydell +# +# 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= ); +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= ); +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 + startprocess '&DUMPOW',"$nice ".$rstr.$dumpcmd; + if ($gz) { + startprocess '<&DUMPOR','>&GZOW',"$nice gzip -v$gz"; + } + startprocess "<&$bufir",'>&BUFOW',"$nasty writebuffer"; + startprocess '<&BUFOR','>/dev/null',"$nasty $ddcmd"; + closepipes(); + endprocesses(); + # advance is a file counter, so it needs to be updated for each + # dump we do to tape. + $advance++; + + finfsys(); +} + +# Rewind the tape, and increment the counter of incremental backups. +runsystem("mt -f $tape rewind"); +open IAN,">increm-advance.new" or die $!; +print IAN "$advance\n" or die $!; +close IAN or die $!; +rename 'increm-advance.new','increm-advance' or die $!; + +pboth("Next FULL dump tape is $next\n"); + +setstatus "INCREMENTAL successful: $tapedesc, next full is $next"; +exit 0; diff --git a/backup/iwjbackup.txt b/backup/iwjbackup.txt new file mode 100644 index 0000000..a353e75 --- /dev/null +++ b/backup/iwjbackup.txt @@ -0,0 +1,204 @@ +iwjbackup.txt +documentation file +Copyright - AND NO WARRANTY - see notes at bottom of file for details. + +This is a quick summary of the backup scripts, and some comments on +some of the config files: it's a bit patchy and might have the odd +ommission. The canonical source is the sources, as always :-> + + +To run, the contents of /etc/chiark-backup should be: + +warnings.*: files defining how many warnings you get as the system is +brought down to do backups. The defaults are fine. + +settings.pl: generic config file: in particular, the name of the tape +device is set here. + +settings.sh: generic config file for shell scripts. Currently only +contains some options for the lvm snapshotter. + +tape.*: conventionally, each tape you're going to use in the backup +cycle has a tape number, a name and a config file. The tape numbers +in use at Relativity are digit strings like `512'. The name is a +combination of rotation set and volume number; rotation sets are +typically a single letter (`s', `t', `u', `v') at Relativity and +volumes a single digit (`0', `1', `2') at Relativity. You need at +least two tapes as the system won't write a backup on the same tape it +wrote the last one to. + +There are also conventionally incremental tapes whose names are a +fixed letter (`k' in the current scheme) followed by a rotation +letter. At Relativity we have two of these, `ks' and `kt'. + +Syntax of the tape.* files for full dump tapes: + filesystems X + next N + end + +where N is the name of the next tape in the *full dump* sequence +(which should be circular; eg +v0->v1->v2->s0->s1->s1->t0->t1->t2->u0->u1->u2->v0->... +and X is a filesystem group name (typically the same as the volume +number). + +Each defined filesystem group has a name and a config file +fsys.. 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 + Other lines should be of the form + [:] [,] + for local backups, or + [:] [,] + for remote backups. +The file (including any included files) must end with the word 'end' +on a line of its own. + +Valid values for are + cpio + uses cpio to produce tar-format backups + dump + uses dump to dump entire filesystems + 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. + + is a comma-separated list of

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; ipw_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=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 != orgmaingid) { + r= setgid(orgmaingid); + if (r>=0) { fputs("could setgid back",stderr); exit(-1); } + if (errno != EPERM) { + perror("unexpected failure mode for setgid back"); exit(-1); + } + } + r= getgid(); if (r<0) { perror("getgid failed"); exit(-1); } + if (r != maingid) { fputs("getgid mismatch",stderr); exit(-1); } + r= getegid(); if (r<0) { perror("getegid failed"); exit(-1); } + if (r != maingid) { fputs("getegid mismatch",stderr); exit(-1); } + } + if (!*argv) { + cp= getenv("SHELL"); + if (!cp) cp= "sh"; + execlp(cp,cp,"-i",(const char*)0); + } else { + execvp(argv[0],(char**)argv); + } + perror("exec failed"); + exit(-1); +} diff --git a/cprogs/really.testcases b/cprogs/really.testcases new file mode 100755 index 0000000..153e47c --- /dev/null +++ b/cprogs/really.testcases @@ -0,0 +1,164 @@ +#!/usr/bin/perl + +$testuser= 'testac'; +$testgroup= 'testac'; +$testuid= 1000; +$testgid= 1000; +@testxgids= qw(1000); +$numgid= 50008; +$othergroup= 'daemon'; +$othergid= 1; + +sub parseid ($) { + my ($id) = @_; + my $orgid= $id; + my $out= ''; + my $part; + chomp($id); + $id =~ s/^uid=// or return $id =~ m/\n/ ? "ERROR: $`" : "ERROR: $id"; + $id =~ s/^(\d+)// or return $orgid; + $out= $1; + $id =~ s/^\([^\)]+\)//; $id =~ s/^\s+// or return $orgid; + $id =~ s/^gid=(\d+)// or return $orgid; + $out.= " $1"; + $id =~ s/^\([^\)]+\)//; $id =~ s/^\s+// or return $orgid; + $id =~ s/^groups=// or return $orgid; + for $part (split(/,/,$id)) { + $part =~ s/^(\d+)// or return $orgid; + $out.= " $1"; + $part =~ s/^\([^\)]+\)//; $part eq '' or return $orgid; + } + return $out; +} + +$org= `id`; +$org =~ m/^uid=\d+\(([^\)]+)\) gid=\d+\(([^\)]+)\) / or die "$org ?"; +$orguser= $1; $orggroup= $2; +$org= parseid($org); +$org =~ m/^\d+ \d+ / or die $org; +($orguid,$orggid,@orgxgids)= split(/ /,$org); + +$tests= <&1`; + $newout= parseid($out); + print("OK $tests[$i] ($tests[$i+2])\n"), next if $newout eq $tests[$i+1]; + die "$newout != $tests[$i+1] ($tests[$i]) $i"; +} diff --git a/cprogs/rwbuffer.c b/cprogs/rwbuffer.c new file mode 100644 index 0000000..4d8503c --- /dev/null +++ b/cprogs/rwbuffer.c @@ -0,0 +1,130 @@ +/* + * rwbuffer.c + * common definitions for readbuffer/writebuffer + * + * readbuffer and writebuffer are: + * Copyright (C) 1997-1998,2000-2001 Ian Jackson + * + * 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 + * Copyright (C) 1999 Peter Maydell + * + * 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] []\n",progname) < 0) + { perror("print usage"); exit(16); } +} + +static void usageerr(const char *what) { + fprintf(stderr,"%s: bad usage: %s\n",progname,what); + usage(stderr); + exit(12); +} + +void nonblock(int fd, int yesno) { + int r; + r= fcntl(fd,F_GETFL,0); if (r == -1) { perror("fcntl getfl"); exit(8); } + if (yesno) r |= O_NDELAY; + else r &= ~O_NDELAY; + if (fcntl(fd,F_SETFL,r) == -1) { perror("fcntl setfl"); exit(8); } +} + +static void unnonblock(void) { + nonblock(0,0); nonblock(1,0); +} + +void startupcore(void) { + buf= xmalloc(buffersize); + + if (opt_mlock) { + if (mlock(buf,buffersize)) { perror("mlock"); exit(2); } + } + + used=0; wp=rp=buf; seeneof=0; + if (atexit(unnonblock)) { perror("atexit"); exit(16); } +} + +void startup(const char *const *argv) { + const char *arg; + char *ep; + int shift=-1; + + assert(argv[0]); + + while ((arg= *++argv)) { + if (!strcmp(arg,"--mlock")) { + opt_mlock= 1; + } else if (isdigit((unsigned char)arg[0])) { + buffersize= strtoul(arg,&ep,0); + if (ep[0] && ep[1]) usageerr("buffer size spec. invalid"); + switch (ep[0]) { + case 0: case 'm': shift= 20; break; + case 'k': shift= 10; break; + case 'b': shift= 0; break; + default: usageerr("buffer size unit unknown"); + } + if (buffersize > ((RWBUFFER_SIZE_MB_MAX << 20) >> shift)) + usageerr("buffer size too big"); + buffersize <<= shift; + } else { + usageerr("invalid option"); + } + } + + startupcore(); + nonblock(0,1); nonblock(1,1); +} + +void *xmalloc(size_t sz) { + void *r= malloc(sz); if (!r) { perror("malloc"); exit(6); }; return r; +} + +void callselect(void) { + int r; + + for (;;) { + r= select(maxselfd,&readfds,&writefds,0,0); + if (r != -1) return; + if (errno != EINTR) { + perror("select"); exit(4); + } + } +} diff --git a/cprogs/rwbuffer.h b/cprogs/rwbuffer.h new file mode 100644 index 0000000..f49b30b --- /dev/null +++ b/cprogs/rwbuffer.h @@ -0,0 +1,77 @@ +/* + * rwbuffer.h + * common declarations for readbuffer/writebuffer + * + * readbuffer and writebuffer are: + * Copyright (C) 1997-1998,2000-2001 Ian Jackson + * + * 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 + * Copyright (C) 1999 Peter Maydell + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dlist.h" + + +int min(int a, int b); +void callselect(void); +void startup(const char *const *argv); +void startupcore(void); +void *xmalloc(size_t sz); +void nonblock(int fd, int yesno); + +extern const char *progname; /* must be defined by main .c file */ + +extern unsigned char *buf, *wp, *rp; +extern int used, seeneof, maxselfd; +extern size_t buffersize; +extern fd_set readfds; +extern fd_set writefds; + + +void wrbufcore_startup(void); +void wrbufcore_prepselect(int rdfd, int wrfd); +void wrbufcore_afterselect(int rdfd, int wrfd); +void fdsetset(int fd, fd_set *set); +void wrbuf_report(const char *m); + + +#endif /*RWBUFFER_H*/ diff --git a/cprogs/smtpallow.c b/cprogs/smtpallow.c new file mode 100644 index 0000000..b4f8765 --- /dev/null +++ b/cprogs/smtpallow.c @@ -0,0 +1,63 @@ +/* + * smtpallow.c - ld_preload for hacking with connect() ! + * + * Copyright (C) 1994,1995 Ian Jackson + * + * 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 +#include +#include +#include + +_syscall2(long,socketcall,int,call,unsigned long *,args); + +int real_connect(int sockfd, const struct sockaddr *saddr, int addrlen) +{ + unsigned long args[3]; + + args[0] = sockfd; + args[1] = (unsigned long)saddr; + args[2] = addrlen; + return socketcall(SYS_CONNECT, args); +} + +int connect(int fd, const struct sockaddr *them_any, int addrlen) { + struct sockaddr_in *them= (struct sockaddr_in*)them_any; + int r,l,i; + struct sockaddr_in us; + + if (addrlen == sizeof(us) && + them->sin_family == AF_INET && + them->sin_port == htons(25)) { + memset(&us,0,sizeof(us)); + us.sin_port= 0; + us.sin_family= AF_INET; + us.sin_addr.s_addr= INADDR_ANY; + r= getsockname(fd,(struct sockaddr*)&us,&l); + if (r<0 && errno != EINVAL) return r; + if (!ntohs(us.sin_port)) { + for (i=1023; i>0; i--) { + us.sin_port= htons(i); + if (!bind(fd,(struct sockaddr*)&us,sizeof(us))) break; + if (errno != EADDRINUSE) return -1; + } + if (!i) return -1; + } else if (r<0) return r; + } + return real_connect(fd,them_any,addrlen); +} diff --git a/cprogs/summer.1 b/cprogs/summer.1 new file mode 100644 index 0000000..a77b8c9 --- /dev/null +++ b/cprogs/summer.1 @@ -0,0 +1,157 @@ +.TH SUMMER "1" "December 2006" "Debian" "Chiark-utils-bin" +.SH NAME +summer \- print checksum and system metainformation for files +.SH SYNOPSIS +.B summer -ACDbfqtx +.RI [\| startpoint ...] +.br +.SH DESCRIPTION +.B summer +prints the MD5 checksum of the contents, and the system +metainformation (ownership, permissions, timestamps, etc.), for a +file, or recursively for a whole directory tree. + +Each command line argument should be a file or directory to be processed; +if it is a directory then it will be processed and then its contents will +also be processed, recursively. If no +.IR startpoint s +are specified on the command line then +.B summer +will read a list of newline-separated startpoints from standard input. + +Since +.B summer +correctly handles devices, FIFOs and other non-regular files it is useful +for generating and comparing summaries of arbitrary directory trees where +md5sum alone would not be. +.SH OUTPUT FORMAT +.B summer +prints one line of information for each filesystem object it processes. +Each line has the following columns: +.TS +tab (@); +l l. +@MD5 checksum (in hex) or file type information +@Size of file in bytes +@File access rights (in octal) +@User ID of owner (in decimal) +@Group ID of owner (in decimal) +@atime (time of last access, decimal time_t) +@mtime (time of last modification) +@ctime (time of last status change) +@Filename +.TE + +For regular files, the first column is the md5sum. For directories, pipes, +symlinks and sockets it is the literal string \fBdir\fR, \fBmountpoint\fR, \fBpipe\fR, \fBsymlink\fR or \fBsocket\fR +as appropriate. For devices it begins with \fBc\fR for character or \fBb\fR for block +devices, followed by the device number as a single 32 bit hex number and as +four separate 8 bit decimal numbers (most significant first). + +Note that any bytes in the filename other than printing 7-bit ASCII +are escaped using +.B \\\x\c +.I NN +syntax, where +.I NN +are two hex digits; backslashes are also escaped in this way. +This makes the output unambiguous. Filenames will be relative +if the relevant +.I startpoint +was relative, and absolute if it was absolute. + +For symlinks the filename column is followed by `\fB -> \fR' (note the +spaces) and the target of the link, again escaped, as above. +.SH OPTIONS +.TP +.B \-A +Do not print the atime (time of last access). The atime column will be omitted. +.TP +.B \-C +Do not print the ctime (time of last status change). The ctime column will be omitted. +.TP +.B \-M +Do not print the mtime (time of last modification). The mtime column will be omitted. +.TP +.B \-D +Do not print directory sizes. The size column for directories will read \fBdir\fR. +.TP +.B \-b +Do not print mtime (time of last modification) for symbolic links. The mtime field +for symbolic links will read \fBlink\fR. +.TP +.B \-B +Do not print any times for special files, symlinks, +sockets, or fifos. The atime, mtime and ctime fields +for these objects will read +.BR char ", " block ", " link ", " sock " or " pipe +instead. +.TP +.B \-f +Include information about errors encountered (for example, unreadable files) +in the output, and continue processing. The default is to print error information +to standard error and stop immediately an error is encountered. +.TP +.B \-x +Do not cross mountpoints while recursing into subdirectories. +Startpoints which are mountpoints \fIare\fR descended into. +.TP +.B \-q +Suppress the progress information which +.B summer +normally prints to standard error. +.TP +.B \-t +Set the field separator between the information and the filename to a +tab character (default is space). +.TP +.B \-f +Normally any errors (problems accessing files including nonexistent +startpoings, and the like) are fatal; an error message is reported to +stderr. + +With +.B -f +errors are nonfatal and the problems are reported inline. The +filesystem object with the problem is reported in the normal way +except that instead of the checksum, the string +\fB\\[\fR\fIproblem\fR[\fB:\fR\ \fIdetails\fR]\fB]\fR +appears. Fields whose value could not be determined are printed +as \fB?\fR. +.TP +.B \-h +Print a brief usage message to stderr (and do nothing else, exiting nonzero). +.SH PARSING THE OUTPUT +If the first character in the line is \fB\\[\fR, then the first +(checksum or type) field is everything until the first subsequent +\fB]\fR; this may be of variable length and will be followed by one or +more spaces. Otherwise the first field has a fixed width: 64 +characters, the size of an MD5 checksum represented in hex, and is +followed by a single space. + +The metadata fields are space-separated but are also space-padded to a +minimum width: 10 characters for sizes and times and ids; 4 characters +for the mode. + +The filename field, and optional link target information, are of +variable length, but they are escaped so that they do not contain +spaces. +.SH AUTHOR +.B summer +is +.br +Copyright (C) 2003-2007 Ian Jackson + +This manpage was written by Peter Maydell +and subsequently improved by Ian Jackson. It is +.br +Copyright (C) 2006 Peter Maydell +.br +Copyright (C) 2007 Ian Jackson + +This is free software, distributed under the GNU General Public +Licence, version 3 or (at your option) any later version; see +/usr/share/doc/chiark-utils-bin/copyright or +/usr/share/common-licenses/GPL-3 +for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/cprogs/summer.c b/cprogs/summer.c new file mode 100644 index 0000000..762b5d6 --- /dev/null +++ b/cprogs/summer.c @@ -0,0 +1,450 @@ +/* + * summer - program for summarising (with md5 checksums) filesystem trees + * + * usage: + * cat startpoints.list | summer >data.list + * summer startpoints... >data.list + * prints md5sum of data-list to stderr + */ +/* + * Copyright (C) 2003,2006-2007 Ian Jackson + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; ist_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(rst_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; id_name); + node(buf, nodeflags, fs); + free(*de); + } + free(namelist); +} + +static void from_stdin(void) { + char buf[MAXFN+2]; + char *s; + int l; + + if (!quiet) + fprintf(stderr, "summer: processing stdin lines as startpoints\n"); + for (;;) { + s= fgets(buf,sizeof(buf),stdin); + if (ferror(stdin)) { perror("summer: stdin"); exit(12); } + if (!s) { if (feof(stdin)) return; else abort(); } + l= strlen(buf); + assert(l>0); + if (buf[l-1]!='\n') { fprintf(stderr,"summer: line too long\n"); exit(8); } + buf[l-1]= 0; + process(buf); + } +} + +int main(int argc, const char *const *argv) { + const char *arg; + int c; + + errfile= stderr; + + while ((arg=argv[1]) && *arg++=='-') { + while ((c=*arg++)) { + switch (c) { + case 'h': + fprintf(stderr, + "summer: usage: summer startpoint... >data.list\n" + " cat startpoints.list | summer >data.list\n"); + exit(8); + case 'q': + quiet= 1; + break; + case 't': + filenamefieldsep= '\t'; + break; + case 'D': + hidedirsize= 1; + break; + case 'b': + hidelinkmtime= 1; + break; + case 'B': + hidextime= 1; + break; + case 'x': + onefilesystem= 1; + break; + case 'C': + hidectime= 1; + break; + case 'A': + hideatime= 1; + break; + case 'M': + hidemtime= 1; + break; + case 'f': + errfile= stdout; + break; + default: + fprintf(stderr,"summer: bad usage, try -h\n"); + exit(8); + } + } + argv++; + } + + if (!argv[1]) { + from_stdin(); + } else { + if (!quiet) + fprintf(stderr, "summer: processing command line args as startpoints\n"); + while ((arg=*++argv)) { + process(arg); + } + } + if (ferror(stdout) || fclose(stdout)) { + perror("summer: stdout (at end)"); exit(12); + } + if (!quiet) + fputs("summer: done.\n", stderr); + return 0; +} diff --git a/cprogs/trivsoundd-start b/cprogs/trivsoundd-start new file mode 100755 index 0000000..9b50700 --- /dev/null +++ b/cprogs/trivsoundd-start @@ -0,0 +1,12 @@ +#!/bin/bash +set -e +pf=`tempfile` +rm -f $pf +mknod -m600 $pf p +exec 3<>$pf +exec >$pf 4<$pf +rm -f $pf +exec 3>&- +(exec logger -p user.info -t trivsoundd <&4 >/dev/null 2>&1) & +exec 4<&- 2>&1 + * + * 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 + * Copyright (C) 1999 Peter Maydell + * + * 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" + " - &%d", sdev); + r= system(cbuf); if (r) { fprintf(stderr,"sox gave %d\n",r); exit(5); } +} + +void wrbuf_report(const char *m) { + printf("writing %s\n", m); +} + +static void selectcopy(void) { + int slave= inq.head ? inq.head->fd : -1; + wrbufcore_prepselect(slave, sdev); + fdsetset(master,&readfds); + callselect(); + wrbufcore_afterselect(slave, sdev); +} + +static void expireoldconns(void) { + struct inqnode *searchold, *nextsearchold; + + for (searchold= inq.head ? inq.head->next : 0; + searchold; + searchold= nextsearchold) { + nextsearchold= searchold->next; + if (searchold->accepted < now-maxstartdelay) { + printf("expired %p\n",searchold); + LIST_UNLINK(inq,searchold); + free(searchold); + } + } +} + +static void acceptnewconns(void) { + static int bad; + + int slave; + struct inqnode *new; + + if (!FD_ISSET(master,&readfds)) return; + + slave= accept(master,0,0); + if (slave < 0) { + if (!(errno == EINTR || + errno == EAGAIN || + errno == EWOULDBLOCK)) { + perror("accept"); + bad++; + if (bad > maxbadaccept) { + fprintf(stderr,"accept failures repeating\n"); + exit(4); + } + } + /* any transient error will just send us round again via select */ + return; + } + + bad= 0; + new= xmalloc(sizeof(struct inqnode)); + new->accepted= now; + new->fd= slave; + LIST_LINK_TAIL(inq,new); + + printf("accepted %p\n",new); +} + +static void switchinput(void) { + struct inqnode *old; + if (!seeneof) return; + old= inq.head; + assert(old); + printf("finished %p\n",old); + close(old->fd); + LIST_UNLINK(inq,old); + free(old); + seeneof= 0; +} + +int main(int argc, const char *const *argv) { + assert(argv[0]); + if (!argv[1] || argv[2] || argv[1][0]=='-') + usageerr("no options allowed, must have one argument (bindname)"); + + buffersize= 44100*4* 5/*seconds*/; + + opensounddevice(); + bindmaster(argv[1]); + nonblock(sdev,1); + nonblock(master,1); + + startupcore(); + wrbufcore_startup(); + + printf("started\n"); + for (;;) { + selectcopy(); + if (time(&now)==(time_t)-1) { perror("time(2)"); exit(4); } + expireoldconns(); + acceptnewconns(); + switchinput(); + } +} diff --git a/cprogs/usernice.c b/cprogs/usernice.c new file mode 100644 index 0000000..1f05e19 --- /dev/null +++ b/cprogs/usernice.c @@ -0,0 +1,67 @@ +/**/ + +#include +#include +#include +#include +#include +#include +#include + +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 \n" + " usernice p ...\n" + " usernice u ...\n", + stderr); + exit(-1); + } + l= strtol(*++argv,&ep,10); + if (*ep == 'p' || *ep == 'u') { mrenice= *ep++; } + if (*ep) { fputs("usernice: priority not numeric or bad flags\n",stderr); exit(-1); } + if (l<-20 || l>20) + { fputs("usernice: priority must be -20 .. 20\n",stderr); exit(-1); } + newprio= l; + if (mrenice) { + eflag=0; + while (*++argv) { + if (mrenice == 'p') { + wrenice= PRIO_PROCESS; + l= strtoul(*argv,&ep,10); + if (*ep) { + fprintf(stderr,"usernice: pid `%s' not numeric\n",*argv); eflag=2; + continue; + } + } else { + wrenice= PRIO_USER; + l= strtoul(*argv,&ep,10); + if (*ep) { + pw= getpwnam(*argv); + if (!pw) { + fprintf(stderr,"usernice: unknown user `%s'\n",*argv); eflag=2; + continue; + } + l= pw->pw_uid; + } + } + if (setpriority(wrenice,l,newprio)) { + perror(*argv); if (!eflag) eflag=1; + } + } + exit(eflag); + } else { + if (setpriority(PRIO_PROCESS,0,newprio)) + { perror("usernice: setpriority"); exit(-1); } + ruid= getuid(); if (ruid == (uid_t)-1) { perror("usernice: getuid"); exit(-1); } + if (setreuid(ruid,ruid)) { perror("usernice: setreuid"); exit(-1); } + execvp(argv[1],argv+1); perror("usernice: exec"); exit(-1); + } +} diff --git a/cprogs/usr-local-src-misc-Makefile b/cprogs/usr-local-src-misc-Makefile new file mode 100644 index 0000000..c2bfd7a --- /dev/null +++ b/cprogs/usr-local-src-misc-Makefile @@ -0,0 +1,22 @@ +CFLAGS= -Wall -Wwrite-strings -Wmissing-prototypes -Wstrict-prototypes \ + -Wpointer-arith -O2 -g -DREALLY_CHECK_FILE='"/etc/inittab"' +LDFLAGS= + +TARGETS=really ucgi ucgitarget + +all: $(TARGETS) + +ucgi: ucgi.o ucgicommon.o + +ucgitarget: ucgitarget.o ucgicommon.o + +really: really.o myopt.o + +really-test: really Makefile + rm -f really-test + cp really really-test + really chown root.staff really-test + really chmod 4770 really-test + +really-check: really-test really.testcases + ./really.testcases diff --git a/cprogs/watershed.c b/cprogs/watershed.c new file mode 100644 index 0000000..3bf9b07 --- /dev/null +++ b/cprogs/watershed.c @@ -0,0 +1,488 @@ +/* + * watershed - an auxiliary verb for optimising away + * unnecessary runs of idempotent commands + * + * watershed is Copyright 2007 Canonical Ltd + * written by Ian Jackson + * 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: + * -d|--state-dir + * default is /var/run/watershed for uid 0 + * $HOME/.watershed for others + * -i|--command-id + * + * files used: + * /.lock lockfile + * /.cohort cohort + * + * default 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 '; exit 1; fi + // cd "$1" + // for f in ./*.lock; do + // with-lock-ex -w rm -f "${f%.lock}.cohort" + // done +/* + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 [] ...\n" + "options: -d|--state-dir -i|--command-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)); +} diff --git a/cprogs/with-lock-ex.1 b/cprogs/with-lock-ex.1 new file mode 100644 index 0000000..941d1d9 --- /dev/null +++ b/cprogs/with-lock-ex.1 @@ -0,0 +1,147 @@ +.TH WITH-LOCK-EX "1" "July 2003" "Debian" "Chiark-utils-bin" +.SH NAME +with-lock-ex \- file locker +.SH SYNOPSIS +.B with-lock-ex +.BR \-w \||\| \-q \||\| \-f +.I lockfile command +.IR args \ \|.\|.\|. +.br +.SH DESCRIPTION +with-lock-ex will open and lock the lockfile for writing and then feed +the remainder of its arguments to +.BR exec (2); +when that process terminates the fd will be closed and the file +unlocked automatically by the kernel. +.PP +If the file does not exist it is created, with permissions +.B rw +for each user class for which the umask has +.BR w . +.SH OPTIONS +.TP +.B \-w +Wait for the lock to be available. +.TP +.B \-f +Fail (printing a message to stderr and exiting 255) if the lock cannot +be acquired immediately because another process has it. +.TP +.B \-q +Silently do nothing (ie, exit 0 instead of executing the specified +process) if the lock cannot be acquired immediately because another +process has it. +.SH STALE LOCKS +The locking protocol used does not suffer from stale locks. If the +lock cannot be acquired, one or more running processes must currently +hold the lock; if the lock needs to be freed those processes should be +killed. +.PP +Under no circumstances should `stale lock cleaner' cron jobs, or the +like, be instituted. In systems where a great many locks may exist, +old lockfiles may be removed from cron but only if each lock is +acquired before the lockfile is removed, for example with +.IP +.B with-lock-ex -q +.I lockfile +.B rm +.I lockfile +.SH DEADLOCKS +There is no deadlock detection. In a system with several locks, a +lock hierarchy should be established, such that for every pair of +locks +.I A +and +.I B +which a process might lock simultaneously, either +.IR A > B +or +.IR B > A +where the relation > is transitive and noncyclic. +.PP +Then, for any two locks +.I X +and +.I Y +with +.IR X > Y +it is forbidden to acquire +.I X +while holding +.IR Y . +Instead, acquire +.I X +first, or release +.I Y +before (re)acquiring +.I X +and +.I Y +in that order. +.PP +(There are more complicated ways of avoiding deadlocks, but a lock +hierarchy is simple to understand and implement. If it does not meet +your needs, consult the literature.) +.SH LOCKING PROTOCOL +The locking protocol used by +.B with-lock-ex +is as follows: +.PP +The lock is held by a process (or group of processes) which holds an +fcntl exclusive lock on the first byte of the plain file which has the +specified name. A holder of the lock (and only a holder of the lock) +may delete the file or change the inode to which the name refers, and +as soon as it does so it ceases to hold the lock. +.PP +Any process may create the file if it does not exist. There is no +need for the file to contain any actual data. Indeed, actually using +the file for data storage is strongly disrecommended, as this will +foreclose most strategies for reliable update. Use a separate +lockfile instead. +.PP +Ability to obtain the lock corresponds to write permission on the file +(and of course permission to create the file, if it does not already +exist). However, processes with only read permission on the file can +prevent the lock being acquired at all; therefore lockfiles should not +usually be world-readable. +.PP +When a (group of) processes wishes to acquire the lock, it should open +the file +(with +.BR O_CREAT ) +and lock it with +.BR fcntl (2) +.BR F_RWLCK , +operation +.B F_SETLK +or +.BR F_SETLKW . +If this succeeds it should fstat the file descriptor it has, and the +file by its path. If the device and inode match then the lock has +been acquired and remains acquired until that group of processes +changes which file the name refers to, deletes the file, or releases +the fcntl lock. If they do not then another process acquired the lock +and deleted the file in the meantime; you must now close your +filedescriptor and start again. +.B with-lock-ex +follows this specification. +.PP +Note that +.BR flock (2) +is a different kind of lock to +.BR fcntl (2). +.B with-lock-ex +uses +.BR fcntl . +.SH AUTHOR +This Manual page was written by Matthew Vernon +and enhanced by Ian Jackson , in 2003, but +may be used by anyone. +.SH COPYRIGHT +with-lock-ex was written by Ian Jackson +in 1993, 1994, 1995, 1996, 1998, 1999. He has placed it in the public +domain. +.SH "SEE ALSO" +.BR fcntl (2), +.BR flock (2), +.BR chmod (2) diff --git a/cprogs/with-lock-ex.c b/cprogs/with-lock-ex.c new file mode 100644 index 0000000..1850d1f --- /dev/null +++ b/cprogs/with-lock-ex.c @@ -0,0 +1,95 @@ +/* + * File locker + * + * Usage: with-lock-ex - ... + * + * 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 +#include +#include +#include +#include +#include +#include + +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 ...\n" + " with-lock ...\n", + stderr); + exit(255); + } else { + argv++; argc--; + } + cmd= argv[2]; + um= umask(0777); if (um==-1) fail("find umask"); + if (umask(um)==-1) fail("reset umask"); + + for (;;) { + + fd= open(argv[1],O_RDWR|O_CREAT,0666&~(um|((um&0222)<<1))); + if (fd<0) fail(argv[1]); + + for (;;) { + fl.l_type= F_WRLCK; + fl.l_whence= SEEK_SET; + fl.l_start= 0; + fl.l_len= 1; + if (fcntl(fd, mode=='w' ? F_SETLKW : F_SETLK, &fl) != -1) break; + if (mode=='q' && + (errno == EAGAIN || errno == EWOULDBLOCK || errno == EBUSY)) + exit(0); + if (errno != EINTR) fail("could not acquire lock"); + } + + if (fstat(fd, &fstab)) fail("could not fstat lock fd"); + if (stat(argv[1], &stab)) { + if (errno != ENOENT) fail("could not stat lockfile"); + } else { + if (stab.st_dev == fstab.st_dev && + stab.st_ino == fstab.st_ino) break; + } + close(fd); + } + + cloexec= fcntl(fd, F_GETFD); if (cloexec==-1) fail("fcntl F_GETFD"); + cloexec &= ~1; + if (fcntl(fd, F_SETFD, cloexec)==-1) fail("fcntl F_SETFD"); + + execvp(cmd,argv+2); + fail("unable to execute command"); +} diff --git a/cprogs/wrbufcore.c b/cprogs/wrbufcore.c new file mode 100644 index 0000000..65fc2be --- /dev/null +++ b/cprogs/wrbufcore.c @@ -0,0 +1,95 @@ +/* + * wrbufcore.c + * + * Core of algorithm for writing output to devices which don't like + * constant stopping and starting, such as tape drives. This is: + * Copyright (C) 1997-1998,2000-2001 Ian Jackson + * + * 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 + * Copyright (C) 1999 Peter Maydell + * + * 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=0 && FD_ISSET(rdfd,&readfds)) && + !used) { + wrbuf_report("stopping"); + writing= 0; + FD_CLR(wrfd,&writefds); + } + + if (rdfd>=0 && FD_ISSET(rdfd,&readfds)) { + r= read(rdfd,rp,min(buffersize-1-used,buf+buffersize-rp)); + if (!r) { + seeneof=1; writing=1; + wrbuf_report("seeneof"); + } else if (r<0) { + if (!(errno == EAGAIN || errno == EINTR)) { perror("read"); exit(1); } + } else { + used+= r; + rp+= r; + if (rp == buf+buffersize) rp=buf; + } + if (used > waitfill) { + if (!writing) wrbuf_report("starting"); + writing=1; + } + } + + if (FD_ISSET(wrfd,&writefds) && used) { + r= write(wrfd,wp,min(used,buf+buffersize-wp)); + if (r<=0) { + if (!(errno == EAGAIN || errno == EINTR)) { perror("write"); exit(1); } + } else { + used-= r; + wp+= r; + if (wp == buf+buffersize) wp=buf; + } + } +} diff --git a/cprogs/writebuffer.1 b/cprogs/writebuffer.1 new file mode 100644 index 0000000..b3f28ab --- /dev/null +++ b/cprogs/writebuffer.1 @@ -0,0 +1,29 @@ +.TH writebuffer 1 2001-10-21 chiark-backup +.SH NAME +writebuffer \- write output to devices which don't like constant stopping and starting +.SH SYNOPSIS +.B writebuffer +.RB [ --mlock ] +.RI [ size ] +.SH DESCRIPTION +.B writebuffer +reads data on standard input and writes it to standard output. It +will buffer internally up to \fIsize\fR megabytes and will only write +data when the buffer is at least 75% full or when there is no more +input to fill the buffer. +.PP +\fIsize\fR may also be suffixed with +.BR m ", " k ", or " b +to indicate that it is in megabytes (2^20), kilobytes (2^10) or bytes. +.PP +It is intended for use in situations where many small writes are +undesirable for performance reasons, e.g. tape drives. +.SH OPTIONS +.TP +.B --mlock +Calls +.BR mlock (2) +to lock the buffer into memory. +.SH "SEE ALSO" +.BR readbuffer (1), +.BR mlock (2) diff --git a/cprogs/writebuffer.c b/cprogs/writebuffer.c new file mode 100644 index 0000000..da47cea --- /dev/null +++ b/cprogs/writebuffer.c @@ -0,0 +1,46 @@ +/* + * writebuffer.c + * + * A program for writing output to devices which don't like constant + * stopping and starting, such as tape drives. writebuffer is: + * Copyright (C) 1997-1998,2000-2001 Ian Jackson + * + * 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 + * Copyright (C) 1999 Peter Maydell + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3, + * or (at your option) any later version. + * + * This is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this file; if not, consult the Free Software + * Foundation's website at www.fsf.org, or the GNU Project website at + * www.gnu.org. + * + */ + +#include "rwbuffer.h" + +const char *progname= "writebuffer"; + +void wrbuf_report(const char *m) { } + +int main(int argc, const char *const *argv) { + startup(argv); + wrbufcore_startup(); + while (!seeneof || used) { + wrbufcore_prepselect(0,1); + callselect(); + wrbufcore_afterselect(0,1); + } + exit(0); +} diff --git a/cprogs/xbatmon-simple.c b/cprogs/xbatmon-simple.c new file mode 100644 index 0000000..6ca3294 --- /dev/null +++ b/cprogs/xbatmon-simple.c @@ -0,0 +1,856 @@ +/* + * display outputs, per line: + * + * Remaining: | Empty: | Degraded: + * blue | black | dimgrey discharging + * green | black | dimgrey charging + * cyan | black | dimgrey charged + * grey | black | dimgrey charging&discharching! + * lightgrey | black | dimgrey none of the above + * blue | red | dimgrey discharging - low! + * green | red | dimgrey charging - low + * cyan | red | dimgrey charged - low [1] + * grey | red | dimgrey charging&discharching, low [1] + * ... darkgreen ... no batteries present + * ... yellow ... error + * + * [1] battery must be quite badly degraded + */ +/* + * Copyright (C) 2004 Ian Jackson + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#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<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<= 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<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<=nondegraded_norm)); + + if (then <= 0.0) then= 0.0; + else if (then >= nondegraded_norm) then= nondegraded_norm; + + leftmost_lit= width * then; + leftmost_nondeg= width * nondegraded_norm; + + if (beyond && first_beyond) { + XDrawLine(disp, win, gc_white.gc, 0,i, leftmost_nondeg,i); + first_beyond= 0; + } else { + if (leftmost_lit < leftmost_nondeg) + XDrawLine(disp, win, gc_empty.gc, + leftmost_lit,i, leftmost_nondeg,i); + if (leftmost_lit >= 0) + XDrawLine(disp, win, gc_remain.gc, 0,i, leftmost_lit,i); + } + } +} + +static void initgc(Gcstate *gc_r) { + XGCValues gcv; + + memset(&gcv,0,sizeof(gcv)); + gcv.function= GXcopy; + gcv.line_width= 1; + gc_r->lastfg= gcv.foreground= pix_equilibrium; + gc_r->gc= XCreateGC(disp,win, GCFunction|GCLineWidth|GCForeground, &gcv); +} + +static void colour(unsigned long *pix_r, const char *whichcolour) { + XColor xc; + const char *name; + Status st; + + name= getresource(whichcolour); + if (!name) fail("get colour resource"); + + st= XAllocNamedColor(disp,cmap,name,&xc,&xc); + if (!st) fail(name); + + *pix_r= xc.pixel; +} + +static void initgraphics(int argc, char **argv) { + int xwmgr, r; + const char *geom_string; + XSizeHints *normal_hints; + XWMHints *wm_hints; + XClassHint *class_hint; + int pos_x, pos_y, gravity; + char *program_name_silly; + + program_name_silly= (char*)program_name; + + normal_hints= XAllocSizeHints(); + wm_hints= XAllocWMHints(); + class_hint= XAllocClassHint(); + + if (!normal_hints || !wm_hints || !class_hint) + fail("could not alloc hint(s)"); + + geom_string= getresource("geometry"); + + xwmgr= XWMGeometry(disp,screen, geom_string,"128x32", 0, + normal_hints, + &pos_x, &pos_y, + &width, &height, + &gravity); + + unsigned long parentwindowid; + if (parentwindow) + parentwindowid = strtoul(parentwindow,0,0); + else + parentwindowid = DefaultRootWindow(disp); + + win= XCreateSimpleWindow(disp,parentwindowid, + pos_x,pos_y,width,height,0,0,0); + cmap= DefaultColormap(disp,screen); + +#define C(c,u) colour(&pix_##u, #u "Color"); +#define GC(g) initgc(&gc_##g); + COLOURS +#undef C +#undef GC + + r= XSetWindowBackground(disp,win,pix_degraded); + if (!r) fail("init set background"); + lastbackground= pix_degraded; + + normal_hints->flags= PWinGravity; + normal_hints->win_gravity= gravity; + normal_hints->x= pos_x; + normal_hints->y= pos_y; + normal_hints->width= width; + normal_hints->height= height; + if ((xwmgr & XValue) || (xwmgr & YValue)) + normal_hints->flags |= USPosition; + + wm_hints->flags= InputHint; + wm_hints->input= False; + wm_hints->initial_state= + (getresource_bool("withdrawn",0,0) ? WithdrawnState : + getresource_bool("iconic",0,0) ? IconicState + : NormalState); + + class_hint->res_name= program_name_silly; + class_hint->res_class= program_name_silly; + + XmbSetWMProperties(disp,win, program_name,program_name, + argv,argc, normal_hints, wm_hints, class_hint); + + XSelectInput(disp,win, ExposureMask|StructureNotifyMask); + XMapWindow(disp,win); +} + +static void refresh(void) { + acquiredata(); + show(); +} + +static void newgeometry(void) { + int dummy; + Window dummyw; + + XGetGeometry(disp,win, &dummyw,&dummy,&dummy, &width,&height, &dummy,&dummy); +} + +static void eventloop(void) { + XEvent ev; + struct pollfd pfd; + int r, timeout; + + newgeometry(); + refresh(); + + for (;;) { + XFlush(disp); + + pfd.fd= ConnectionNumber(disp); + pfd.events= POLLIN|POLLERR; + + timeout= !(charging_mask & (1u << CHGST_ERROR)) ? TIMEOUT : TIMEOUT_ONERROR; + r= poll(&pfd,1,timeout); + if (r==-1 && errno!=EINTR) failr("poll",errno); + + while (XPending(disp)) { + XNextEvent(disp,&ev); + if (ev.type == ConfigureNotify) { + XConfigureEvent *ce= (void*)&ev; + width= ce->width; + height= ce->height; + } + } + refresh(); + } +} + +int main(int argc, char **argv) { + parseargs(argc,argv); + initacquire(); + initgraphics(argc,argv); + eventloop(); + return 0; +} diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..02b4972 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,500 @@ +chiark-utils (4.2.0) unstable; urgency=low + + * Rename `xacpi-simple' to `xbatmon-simple': + - rename source file + - change program name in Makefiles, .gitignore, rules, etc. + - change program's idea of its own name for usage message + - look for X resources under both old ane new names + - provide a compatibility symlink (using dh_link) + + -- Ian Jackson 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Sun, 18 Feb 2007 13:02:21 +0000 + +chiark-utils (4.1.13) unstable; urgency=low + + * New `nosnap' no-op snap kind. + + -- Ian Jackson Fri, 16 Feb 2007 19:10:23 +0000 + +chiark-utils (4.1.12) unstable; urgency=low + + * New `watershed' program. + + -- Ian Jackson 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 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 Mon, 2 Oct 2006 22:21:52 +0100 + +chiark-utils (4.1.9) unstable; urgency=low + + * New better algorithm for expire-iso8601. + + -- Ian Jackson 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 Tue, 15 Aug 2006 15:46:19 +0100 + +chiark-utils (4.1.7) unstable; urgency=low + + * backup: new `noinc' fs line option. + + -- Ian Jackson 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Mon, 8 Oct 2001 01:52:21 +0100 + +# Local variables: +# mode: debian-changelog +# End: diff --git a/debian/chiark-backup/conffiles b/debian/chiark-backup/conffiles new file mode 100644 index 0000000..c38afbf --- /dev/null +++ b/debian/chiark-backup/conffiles @@ -0,0 +1,5 @@ +/etc/chiark-backup/settings.sh +/etc/chiark-backup/snap/lvm +/etc/chiark-backup/snap/remount +/etc/chiark-backup/snap/remountrocp +/etc/chiark-backup/snap/nosnap diff --git a/debian/chiark-utils-bin.links b/debian/chiark-utils-bin.links new file mode 100644 index 0000000..becff8f --- /dev/null +++ b/debian/chiark-utils-bin.links @@ -0,0 +1 @@ +/usr/bin/xbatmon-simple /usr/bin/xacpi-simple diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..f99a656 --- /dev/null +++ b/debian/control @@ -0,0 +1,130 @@ +Source: chiark-utils +Section: admin +Priority: extra +Maintainer: Ian Jackson +Build-Depends: libx11-dev, nettle-dev, debhelper (>= 5) +Standards-Version: 3.9.1 + +Package: chiark-backup +Section: utils +Priority: extra +Architecture: all +Depends: chiark-rwbuffer, chiark-utils-bin, ${misc:Depends} +Suggests: chiark-utils-bin (>= 4.1.14) +Description: backup system for small systems and networks + These are the backup scripts used by chiark.greenend.org.uk and other + systems belonging to the Sinister Greenend Organisation. Features: + * Suitable for single systems and small networks. + * Reasonably simple; they do what you tell it to. + * Hard failures when individual systems fail, to encourage fixing ! + If you have a larger site you may wish to look at Amanda. + +Package: chiark-scripts +Section: admin +Priority: extra +Conflicts: chiark-named-conf, sync-accounts +Replaces: chiark-named-conf, sync-accounts +Depends: ${misc:Depends} +Suggests: tcl8.4 +Architecture: all +Description: chiark system administration scripts + This package contains a number of small administration scripts used + by chiark.greenend.org.uk and other systems belonging to the Sinister + Greenend Organisation. Featuring: + . + chiark-named-conf: a tool for managing nameserver configurations + and checking for suspected DNS problems. Its main functions are to + check that delegations are appropriate and working, that secondary + zones are slaved from the right places, and to generate a + configuration for BIND, from its own input file. + . + sync-accounts: a simple but flexible account info synchroniser. + sync-accounts is a tool for copying un*x account data from remote + systems and installing it locally. It is flexible and reasonably + straightforward, but lacks integration with other distributed + databases such as NIS. + . + cvs-repomove and cvs-adjustroot: tools for moving CVS repositories + and adjusting working trees. + . + palm-datebook-reminders: a program which emails mails you reminders + about the appointments in your Palm's Datebook. + . + cvsweb-list: cgi program to list ucgi (userv-utils) cvsweb repos + . + expire-iso8601: keep or expire backup trees named after their dates + . + gnucap2genspic, ngspice2genspic, genspic2gnuplot: convert gnucap + files and ngspice output files to genspic and genspic files to + gnuplot input so they can be plotted. + . + hexterm: connects to serial port and allows the user interact in + ASCII and hex. Ie, a hex "terminal" program which lets you speak a + serial port protocol directly. (Needs tcl8.4 to be installed.) + . + random-word, remountresizereiserfs, + summarise-mailbox-preserving-privacy + +Package: chiark-rwbuffer +Section: utils +Priority: extra +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: readbuffer/writebuffer: prevents tape drive seesawing, etc. + readbuffer and writebuffer: programs for reading input from devices, + and writing output to, which don't like constant stopping and + starting, such as tape drives and audio playback devices. + +Package: chiark-utils-bin +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: ${shlibs:Recommends} +Suggests: ${shlibs:Suggests} +Section: utils +Priority: extra +Description: chiark system administration utilities + This package contains a number of small administration scripts used + by chiark.greenend.org.uk and other systems belonging to the Sinister + Greenend Organisation. Currently featuring only: + . + with-lock-ex: a simple tool for acquiring a lockfile before running + another program or script. + . + summer: a tool for reporting complete details about a filesystem tree + in a parseable format, including checksums. + . + xbatmon-simple: a very simple X client for displaying battery + charge status. + . + watershed: a utility for saving on superfluous executions of an + idempotent command. (This is the same utility as shipped separately + in Ubuntu's udev, but with slightly different defaults and a + different install location.) + . + rcopy-repeatedly: a utility for repeatedly copying a file from one + host to another, to keep a copy constantly up to date. + . + summer and watershed require the installation of the Recommended + crypto libraries; xbatmon-simple needs the Suggested X libraries. + +Package: chiark-really +Section: admin +Priority: extra +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: really - a tool for gaining privilege (simple, realistic sudo) + really is a program that allows certain users to become whatever user + they like on request. It is a bit like sudo in that respect. + However, really is simpler than sudo, and doesn't give the system + administrator any false security promises. So really is less of a + general security risk to the system. + . + Unlike sudo it does not pretend that the called account can be any + more secure than the calling account. so there is never a need for a + password. If you wanted to restrict which commands and functions the + called user can perform, use userv, not really or sudo. + . + Also unlike sudo, really only works if the calling user is supposed + to be equivalent to root. But, really can also be used by + root-equivalent users to become any user, not just root; in this way + it can be a replacement for certain uses of su. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..2d0cbc9 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,79 @@ +This package contains some the utilities from Ian Jackson's private +system `chiark.greenend.org.uk'. + +This package, containing the moderately portable sources and Debian +packaging information, and the resulting Debian binary packages, was +put together by Ian, but with assistance from other chiark users. For +both upstream and Debian packaging questions, please contact +chiark-utils-maint@chiark.greenend.org.uk. + + +The software included is: + +chiark backup, for small systems or networks. chiark backup is + Copyright 1997-1998,2000-2001 Ian Jackson + Copyright 1999 Peter Maydell + +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 + +sync-accounts, a tool for synchronising UN*X password data. It's + Copyright 1999-2000,2002 Ian Jackson + Copyright 2000-2001 nCipher Corporation Ltd + +chiark-named-conf (in the scripts directory) checks and generates +nameserver configurations. It is: + Copyright 2002 Ian Jackson + +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 + +summer, a tool for reporting complete details about a filesystem tree + Copyright 2003-2007 Ian Jackson + manpage Copyright 2006 Peter Maydell + +cvs-repomove and cvs-adjustroot: tools for moving CVS repositories + Copyright 2004-2006 Ian Jackson + +cvsweb-list: cgi program to list ucgi (userv-utils) cvsweb repos + Copyright 2001 Ian Jackson + +expire-iso8601: keeps or expires backup trees named after their dates + Copyright 2006 Ian Jackson + +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 + +hexterm: connect to serial port and interact in ASCII and hex (hex terminal) + Copyright 2005 Ian Jackson + +palm-datebook-reminders: for mailing reminders about Palm PDA appointments + Copyright 2003 Ian Jackson + +random-word, remountresizereiserfs, summarise-mailbox-preserving-privacy + Miscellaneous utilities. + Copyright 2004,2006 Ian Jackson + + +The chiark utilities are all free software; you can redistribute them +and/or modify them under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 of the +License, or (at your option) any later version. + +These programs are distributed in the hope that they will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License with +your Debian GNU/Linux system, in /usr/share/common-licenses/GPL-3, or +with the chiark-utils source package as the file COPYING; if not, +email me at one of the addresses above or consult the Free Software +Foundation's website at www.fsf.org, or the GNU Project website at +www.gnu.org. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..62d84cf --- /dev/null +++ b/debian/rules @@ -0,0 +1,157 @@ +#!/usr/bin/make -f + +SHELL=/bin/bash + +subdirs_build_arch= cprogs +subdirs_nobuild=backup sync-accounts scripts +package= chiark-utils +packages_indep= chiark-backup chiark-scripts +packages_arch= chiark-rwbuffer chiark-really chiark-utils-bin +packages= $(packages_indep) $(packages_arch) + +cwd= $(shell pwd) +d= $(cwd)/debian +t= $d/tmp + +build: + $(checkdir) + set -e; for s in $(subdirs_build_arch); do $(MAKE) -C $$s all; done + touch build + +build-indep: build +build-arch: build + +clean: + $(checkdir) + rm -f build + set -e; for s in $(subdirs_build_arch); do \ + $(MAKE) -C $$s -i distclean || \ + $(MAKE) -C $$s -f Makefile.in distclean; \ + done + rm -rf *~ debian/tmp debian/*~ debian/files* debian/substvars* + rm -rf debian/sv-* debian/*.debhelper.log + +binary-prep: + $(checkdir) + rm -rf debian/tmp* + # + set -e; for s in $(subdirs_build_arch) $(subdirs_nobuild); do \ + $(MAKE) -C $$s install install-docs install-examples \ + prefix=$t/$$s/usr \ + etcdir=$t/$$s/etc \ + varlib=$t/$$s/var/lib \ + mandir=$t/$$s/usr/share/man; \ + done + # + mv $t/cprogs $t/chiark-utils-bin + # + cp -a debian/tmp/sync-accounts/* debian/tmp/scripts/. + rm -r debian/tmp/sync-accounts + mv debian/tmp/scripts debian/tmp/chiark-scripts + mv debian/tmp/backup debian/tmp/chiark-backup + # + set -e; for p in $(packages); do \ + install -d $t/$$p/DEBIAN $t/$$p/usr/share/doc/$$p; \ + cp debian/copyright debian/changelog \ + $t/$$p/usr/share/doc/$$p/; \ + ln -s changelog.gz \ + $t/$$p/usr/share/doc/$$p/changelog.Debian.gz; \ + gzip -9v $t/$$p/usr/share/doc/$$p/changelog; \ + done + # + install -d $t/chiark-rwbuffer/usr/bin + install -d $t/chiark-rwbuffer/usr/share/man/man1 + cd $t/chiark-utils-bin/usr/bin && \ + mv readbuffer writebuffer $t/chiark-rwbuffer/usr/bin/ + cd $t/chiark-utils-bin/usr/share/man/man1 && \ + mv readbuffer.1 writebuffer.1 $t/chiark-rwbuffer/usr/share/man/man1/ + # + install -d $t/chiark-backup/usr/share/man/man1 + cp backup/man/*.1 $t/chiark-backup/usr/share/man/man1/ + cd $t/chiark-backup/usr/share/man/man1 && \ + for m in *.1; do \ + mv "$$m" backup-"$$m"; \ + done + cp \ + $t/chiark-backup/usr/share/doc/chiark-backup/examples/chiark/settings.sh \ + $t/chiark-backup/etc/chiark-backup/settings.sh + # + install -d $t/chiark-really/usr/sbin + install -d $t/chiark-really/usr/share/man/man8 + cd $t/chiark-utils-bin/usr/sbin && \ + mv really $t/chiark-really/usr/sbin/ + cd $t/chiark-utils-bin/usr/share/man/man8 && \ + mv really.8 $t/chiark-really/usr/share/man/man8/ + rm $t/chiark-utils-bin/usr/sbin/trivsoundd \ + $t/chiark-utils-bin/usr/share/man/man8/trivsoundd.8 + rmdir $t/chiark-utils-bin/usr/sbin \ + $t/chiark-utils-bin/usr/share/man/man8 + # + gzip -9f $t/*/usr/share/man/man*/* + +binary-hook-chiark-backup: +binary-hook-chiark-rwbuffer: +binary-hook-sync-accounts: +binary-hook-chiark-scripts: +binary-hook-chiark-really: +binary-hook-chiark-utils-bin: + +binary-one: + set -e; for f in preinst postinst prerm postrm conffiles; do \ + test -f debian/$p/$$f || continue; \ + cp debian/$p/$$f $t/$p/DEBIAN/$$f; \ + chmod u=rwX,go=rX $t/$p/DEBIAN/$$f; \ + done + dh_link -p$p -Pdebian/tmp/$p + dpkg-gencontrol -isp -p$p -P$t/$p -Tdebian/sv-$p + chown -R root.root debian/tmp + chmod -R g-ws debian/tmp + debian/rules binary-hook-$p + dpkg --build $t/$p .. + +binary-indep: checkroot build binary-prep + set -e; for p in $(packages_indep); do \ + debian/rules binary-one p=$$p; done + +binary-arch: checkroot build binary-prep + $(checkdir) + set -ex; for p in chiark-really chiark-utils-bin chiark-rwbuffer; do \ + dh_strip -p$$p -Pdebian/tmp/$$p; done + dpkg-shlibdeps -Tdebian/sv-chiark-rwbuffer \ + $t/chiark-rwbuffer/usr/bin/* + dpkg-shlibdeps -Tdebian/sv-chiark-really \ + $t/chiark-really/usr/sbin/* + set -e; for f in $t/chiark-utils-bin/usr/bin/*; do \ + case "$$f" in \ + */xbatmon-simple) d=Suggests ;; \ + */watershed|*/summer) d=Recommends ;; \ + *) d=Depends ;; \ + esac; \ + a="$$a -d$$d $$f"; \ + done; set -x; \ + dpkg-shlibdeps -Tdebian/sv-chiark-utils-bin $$a + perl -i~ -pe ' '\ + -e' next unless m/^shlibs:/; '\ + -e' s/$$/,/; s/=/=, /; '\ + -e' s/, libgmp3(?:c2)?,/, libgmp3 | libgmp3c2,/; '\ + -e' s/=, /=/; s/,$$//; '\ + debian/sv-*[!~] + set -e; for p in $(packages_arch); \ + do debian/rules binary-one p=$$p; done + +define checkdir + test -f cprogs/writebuffer.c +endef + +# Below here is fairly generic really + +binary: binary-indep binary-arch + +source diff: + @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false + +checkroot: + $(checkdir) + test root = "`whoami`" + +.PHONY: binary binary-arch binary-indep clean checkroot diff --git a/scripts/ChiarkNNTP.pm b/scripts/ChiarkNNTP.pm new file mode 100644 index 0000000..5b69e28 --- /dev/null +++ b/scripts/ChiarkNNTP.pm @@ -0,0 +1,137 @@ +#!/usr/bin/perl + +use strict qw(subs); +use warnings; + +require 5.002; +use Socket; +use FileHandle; + + +BEGIN { + use Exporter (); + our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); + + # set the version for version checking + $VERSION = 1.00; + + @ISA = qw(Exporter); + @EXPORT = qw(cnntp_connect); + %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ], + + @EXPORT_OK = qw(); +} +our @EXPORT_OK; + +sub cnntp_connect ($) { + my ($verbose) = @_; + + my $ns=$ENV{'NNTPSERVER'}; + if (!defined $ns or !length $ns) { + $ns = `cat /etc/nntpserver`; + chomp($ns); + } + my $port = (getservbyname("nntp", "tcp"))[2]; + $ns = inet_aton($ns); + my $proto = getprotobyname("tcp"); + my $paddr = sockaddr_in($port, $ns); + + my $sock = new IO::Handle; + socket($sock,PF_INET,SOCK_STREAM,$proto) or die "socket: $!"; + connect($sock,$paddr) or die "connect: $!"; + + $sock->autoflush(1); + + return bless { S => $sock, V => $verbose }; +} + +sub banner_reader ($) { + my ($c) = @_; + my ($code,$l) = $c->getline(); + $code =~ /^2\d\d/ or die "no initial greeting from server\n"; + $c->docmd("MODE READER"); +} + +sub disconnect ($) { + my ($c) = @_; + close $c->{S}; +} + +sub putline ($$) { + my ($c, $line) = @_; + my $s = $c->{S}; + my $v = $c->{V}; + print $v ">>> $line\n" if $v; + print $s "$line\r\n"; +} + +sub getline_raw ($) { + my ($c) = @_; + my $s = $c->{S}; + my $l = <$s>; + return $l; +} + +sub getline ($) { + my ($c) = @_; + my $v = $c->{V}; + my $l = $c->getline_raw(); + $l =~ s/[\r\n]*$//s; + my $code = substr($l,0,3); + print $v "<<< $l\n" if $v; + return ($code,$l); +} + +sub docmd ($$;$) { + my ($c,$cmd,$nocheck) = @_; + my ($code,$l); + for my $n (0,1) { + $c->putline($cmd); + ($code,$l) = $c->getline(); + if ($code eq "480") { $c->auth(); } else { last; } + } + if (!$nocheck) { + $code =~ /^2\d\d/ or die "failed on `$cmd':\n$l\n"; + } + return ($code,$l); +} + +sub auth ($) { + my ($c) = @_; + # Authentication. + return if $c->{Authed}++; + my $auth = $ENV{"NNTPAUTH"}; + if (defined $auth) { + $c->putline("AUTHINFO GENERIC $auth"); + pipe AUTHSTDIN, TOAUTH or die "unable to create pipes"; + pipe FROMAUTH, AUTHSTDOUT or die "unable to create pipes"; + flush STDOUT; + my $pid = fork; + if (!defined $pid) { + die "unable to fork for authentication helper"; + } elsif ($pid == 0) { + # we are child + $c->{V} = undef if $c->{V} eq 'STDOUT'; + $ENV{"NNTP_AUTH_FDS"} = "0.1"; + open STDIN, "<&AUTHSTDIN"; + open STDOUT, ">&AUTHSTDOUT"; + close $c->{S}; + exec $auth; + die $!; + } + # we are parent + close AUTHSTDIN; + close AUTHSTDOUT; + autoflush TOAUTH 1; + my ($code,$l) = $c->getline(); print TOAUTH "$l\n"; + while () { + s/[\r\n]*$//s; + $c->putline($_); + ($code,$l) = $c->getline(); + print TOAUTH "$l\n"; + } + die "failed authentication\n" unless $? == 0; + } +} + +1; diff --git a/scripts/Makefile b/scripts/Makefile new file mode 100644 index 0000000..94458b6 --- /dev/null +++ b/scripts/Makefile @@ -0,0 +1,54 @@ +# Makefile + +# This file is part of chiark-utils, a collection of useful programs +# used on chiark.greenend.org.uk. +# +# This file is: +# Copyright (C) 2001 Ian Jackson +# +# This is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3, or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +include ../settings.make + +SCRIPTS= palm-datebook-reminders random-word expire-iso8601 \ + genspic2gnuplot gnucap2genspic ngspice2genspic \ + cvs-repomove cvs-adjustroot remountresizereiserfs \ + hexterm summarise-mailbox-preserving-privacy +MANPAGES1= palm-datebook-reminders + +CSCRIPTS= named-conf +CMANPAGES8= named-conf + +all: + +install: + $(INSTALL_DIRECTORY) $(bindir) + set -e; for f in $(SCRIPTS); do \ + $(INSTALL_SCRIPT) $$f $(bindir)/$$f; done + set -e; for f in $(CSCRIPTS); do \ + $(INSTALL_SCRIPT) $$f $(bindir)/chiark-$$f; done + +install-docs: + $(INSTALL_DIRECTORY) $(man1dir) $(man8dir) + set -e; for f in $(MANPAGES1); do \ + $(INSTALL_SHARE) $$f.1 $(man1dir)/$$f.1; done + set -e; for f in $(CMANPAGES8); do \ + $(INSTALL_SHARE) $$f.8 $(man8dir)/chiark-$$f.8; done + +install-examples: + +clean: + rm -f *~ ./#*# + +distclean realclean: clean diff --git a/scripts/cvs-adjustroot b/scripts/cvs-adjustroot new file mode 100755 index 0000000..0e128cc --- /dev/null +++ b/scripts/cvs-adjustroot @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +usage () { echo >&2 'usage: cvs-adjustroot OLD NEW'; exit 1; } + +case "$#.$1" in +4.--reinvoke) reinvoke=true; shift ;; +*.-*) usage ;; +2.*) reinvoke=false ;; +1.*) usage ;; +0.*) usage ;; +*) usage ;; +esac + +# Copyright 2004 Ian Jackson +# +# This script and its documentation (if any) are free software; you +# can redistribute it and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# chiark-named-conf and its manpage are distributed in the hope that +# it will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + + +old="$1"; shift +new="$1"; shift + +if $reinvoke; then + filename="$1"; + cmp -- "$filename" <(printf "%s\n" "$old") + printf "%s\n" "$new" >"$filename".new + mv -f -- "$filename".new "$filename" + exit 0 +fi + +find -path '*/CVS/Root' -exec cvs-adjustroot --reinvoke "$old" "$new" '{}' ';' diff --git a/scripts/cvs-repomove b/scripts/cvs-repomove new file mode 100755 index 0000000..a3bb60f --- /dev/null +++ b/scripts/cvs-repomove @@ -0,0 +1,278 @@ +#!/bin/bash +set -e +print_usage () { cat <<'END' +usage: + to move repository directory, cd to anywhere and + cvs-repomove --move local-repo module hostname remote-repo + cvs-repomove --move src-hostname src-repo module dst-hostname dst-repo + to adjust CVS/Root afterwards, in each working directory + cvs-repomove automatically update **/CVS/Root +END +} + +# Copyright 2004-2006 Ian Jackson +# +# This script and its documentation (if any) are free software; you +# can redistribute it and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# chiark-named-conf and its manpage are distributed in the hope that +# it will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + + +# We do things in the following order: +# src dst +# 0. Check things @/..moving-to none/..moved-to +# 1. Rename src repo to prevent commits etc. ..moving-to none/..moved-to +# 2. Make temporary copy at destination ..moving-to ..tmp +# 3. Install temporary copy at destination ..moving-to @ +# 4. Move aside src repo ..moved-to @ + +fail () { echo >&2 "error: $1"; exit 8; } +bad_usage () { echo >&2 "bad usage: $1"; print_usage >&2; exit 12; } + +move=false + +while [ $# -gt 0 ]; do + case "$1" in + --move) move=true ;; + --help) print_usage; exit 0 ;; + --) ;; + -*) bad_usage "unknown option $1" ;; + *) break ;; + esac + shift +done + +mn () { echo " $1"; } + +check_module () { + case "$module" in + *..*) fail \ + "moving a module with \`..' in its name is not supported" ;; + */*) fail \ + "moving a subdirectory is not supported" ;; + -*) fail \ + "moving a module whose name starts with \`-' is not supported" ;; + esac +} + +check_hostname () { + case "$1" in + /*|.*|-*) fail "bad hostname $dsthost" ;; + esac +} + +check_remote_path () { + case "$1" in + *[^0-9a-zA-Z/._+,-]*) fail \ + "pathname may not contain metacharacters, sorry" ;; + esac +} + +do_move () { + check_module + check_hostname "$srchost" + check_hostname "$dsthost" + check_remote_path "$srcrepo/$module" + check_remote_path "$dstrepo/$module" + + case "$dstrepo" in + /*) ;; + *) bad_usage "destination repo path must be absolute" ;; + esac + + printf "moving module %s from %s:%s to %s:%s\n" \ + "$module" "$srchost" "$srcrepo" "$dsthost" "$dstrepo" + + mn "checking existing repository" + "$CVS_RSH" "$srchost" bash -ec "' + ls -d -- $srcrepo/CVSROOT >/dev/null + '" + + dstrepotrans="$(printf '%s\n' "$dstrepo" | tr / :)" + movingto="moving-to-$dsthost:$dstrepotrans" + resume="$("$CVS_RSH" "$srchost" bash -ec "' + if test -d $srcrepo/$module..$movingto; then + echo >&2 \" resuming previous attempt at a move\" + resume=true + if test -d $srcrepo/$module; then + echo >&2 \"but $srcrepo/$module exists too\" + exit 1 + fi + else + resume=false + ls -d -- $srcrepo/$module >/dev/null + fi + set +e + previously=\"$(ls -d -- $srcrepo/$module..moved-to-* 2>/dev/null)\" + set -e + if [ \"x\$previously\" != x ]; then + echo >&2 \" btw, module was once before moved away from here\" + mv -- \"\$previously\" \ + \"\${previously/..moved-to-/..previously-\$(date +%s)-moved-to-}\" + fi + echo \$resume + '")" + + mn "checking dst repository" + "$CVS_RSH" "$dsthost" bash -ec "' + cd $dstrepo + ls -d CVSROOT >/dev/null + if test -d $dstrepo/$module; then + echo >&2 module already exists in destination repo + exit 1 + fi + for f in $module..*; do + case \"\$f\" in + *..moved-to-*) + echo \" btw, module was previously at destn repo\" + mv -- \"\$f\" \ + \"\${f/..moved-to-/..previously-\$(date +%s)-moved-to-}\" + ;; + *..previously-*) ;; + *..tmp-*) + echo \" nb: possibly-stale temp/partial copy \$f\" + ;; + *..\*) + ;; + *) + echo >&2 \"error: found unexpected subdir \$f\" + exit 8 + ;; + esac + done + '" + + if ! $resume; then + "$CVS_RSH" "$srchost" bash -ec "' + mv -- $srcrepo/$module $srcrepo/$module..$movingto + '" + fi + + mn "transferring repo data" + tmpid="tmp-$(uname -n).$(date +%s)" + "$CVS_RSH" "$srchost" bash -ec "' + tar -c -C $srcrepo/$module..$movingto -f - . + '" | "$CVS_RSH" "$dsthost" bash -ec "' + cd $dstrepo + mkdir $module..$tmpid + cd $module..$tmpid + tar --no-same-owner -xf - + '" + test "${PIPESTATUS[*]}" = "0 0" + + mn "confirming move at destination repo" + "$CVS_RSH" "$dsthost" bash -ec "' + cd $dstrepo + mv $module..$tmpid $module + '" + + mn "confirming move at source repo" + "$CVS_RSH" "$srchost" bash -ec "' + mv -- $srcrepo/$module..$movingto \ + $srcrepo/$module..moved-to-$dsthost:$dstrepotrans + '" + echo "module moved successfully" +} + +compute_fqdn_data () { + fqdn_data="$(adnshost -s - "$1" 2>/dev/null || true)" +} + +do_furtle () { + module="$(cat CVS/Repository)" + oldroot="$(cat CVS/Root)" + goose="$oldroot" + check_module + printf "checking/updating repo for %s\n" "$module" + compute_fqdn_data "$(uname -n)" + our_fqdn_data="$fqdn_data" + searching=true + while $searching; do + mn "chasing geese at $goose" + case "$goose" in + *[0-9a-zA-Z]:/*) + remotehost="${goose%%:*}" + path="${goose#*:}" + check_hostname "$remotehost" + check_remote_path "$path/$module" + isremote=true + compute_fqdn_data "$remotehost" + if [ "x$fqdn_data" = "x$our_fqdn_data" -a \ + "x$fqdn_data" != x ]; then + isremote=false + goose="$path" + fi + ;; + *:*) + fail "unknown remote repository string $goose" + ;; + /*) + isremote=false + path="$goose" + ;; + *) + fail "unknown repository string $goose" + ;; + esac + check=" + cd $path + if test -d $module; then echo good; exit 0; fi + if ls -d $module..moved-to-* 2>/dev/null; then exit 0; fi + echo bad +" + if $isremote; then + new_goose_info="$("$CVS_RSH" "$remotehost" \ + bash -ec "'$check'")" + else + new_goose_info="$( bash -ec "$check")" + fi + case "$new_goose_info" in + good) + searching=false + ;; + bad) + echo >&2 "trail went cold at $goose" + exit 4 + ;; + *..moved-to-*) + goose="$(printf '%s\n' \ + "${new_goose_info#*..moved-to-}" | \ + tr : / | sed -e 's,/,:,')" + ;; + esac + done + if [ "x$goose" = "x$oldroot" ]; then + echo 'repo has not moved - nothing to do' + exit 0 + fi + mn "found new repo, adjusting working tree" + cvs-adjustroot "$oldroot" "$goose" + echo 'working tree adjustment completed' +} + +if $move; then + if [ $# = 4 ]; then + srchost="$(hostname -f)"; srcrepo="$1" + module="$2"; + dsthost="$3"; dstrepo="$4" + elif [ $# = 5 ]; then + srchost="$1"; srcrepo="$2" + module="$3"; + dsthost="$4"; dstrepo="$5" + else + bad_usage "--move needs hostname(s) and paths" + fi + do_move +else + [ "$#" = 0 ] || bad_usage "without --move, give no arguments" + do_furtle +fi diff --git a/scripts/cvsweb-list b/scripts/cvsweb-list new file mode 100755 index 0000000..c574eb8 --- /dev/null +++ b/scripts/cvsweb-list @@ -0,0 +1,80 @@ +#!/usr/bin/perl +# cvsweb-list +# This little program produces a web page listing the cvs repositories +# available by ucgi cvsweb. It doesn't really separate code and +# configuration, so it's not installed by default with chiark-utils. + +# This file is part of chiark-utils, a collection of useful programs +# used on chiark.greenend.org.uk. +# +# This file is: +# Copyright 2001 Ian Jackson +# +# 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 < +chiark public CVS + + + +

chiark users' public CVS

+
    +END + +open UL, "/etc/userlist" or die $!; +while (
      ) { + 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 "
    • $user" or die $!; + print " (homepage)" or die $! + if -d "/home/$user/public-html"; + print ":" or die $!; + $hd= 1; + } else { + print "," or die $!; + } + print " $mod" or die $!; + } + next unless $hd; + print "
    • \n" or die $!; +} + +close UL or die $!; + +print < +
      +
      + maintained by + $ENV{SERVER_ADMIN}; + chiark home page +
      + +END + +exit 0 diff --git a/scripts/expire-iso8601 b/scripts/expire-iso8601 new file mode 100755 index 0000000..530c48c --- /dev/null +++ b/scripts/expire-iso8601 @@ -0,0 +1,227 @@ +#!/bin/bash +set -e + usage () { + cat <] x [x ...] +options: + -u is measured in units of seconds + (default is 86400, so is in days) + -s allow kept items to be seconds shorter apart than + specified; default is 10% of + -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 x pairs. +exit status: + 0 ok + 4 rm failed + 8 bad usage + 16 catastrophic failure +END + } + +# Copyright 2006 Ian Jackson +# +# 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 x $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 <. +# +# Produces various output files: +# .gnuplots.sh run this to display results +# ,.gnuplot-cmd gnuplot script for displaying: +# ,-.gnuplot-data gnuplot-format input data +# ,gnuplot-fifo working fifo for .gnuplots.sh +# where +# is Freq or Time (according to the type of analysis) +# is the count, starting at 0, of which report this is from gnucap +# 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 <; + 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 < $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 <\$fi +printf 'hit return to quit: ' +read +exec 3>&- +END + or die $!; +close A or die $!; + +print ": generated ; $sof\n" or die $!; + +# $Id: genspic2gnuplot,v 1.6 2007-09-21 21:21:15 ianmdlvl Exp $ diff --git a/scripts/gnucap2genspic b/scripts/gnucap2genspic new file mode 100755 index 0000000..dc150a1 --- /dev/null +++ b/scripts/gnucap2genspic @@ -0,0 +1,109 @@ +#!/usr/bin/perl +# gnucap2genspic - Copyright 2004 Ian Jackson - see below +# +# Reads the output from gnucap and outputs a `genspic' file +# for use with genspic2gnuplot. Takes no arguments or options. +# +# Limitations +# +# Only Freq (.AC) and Time (.TRAN) plots have been tested. If +# other types go wrong they can probably be fixed by adding code for +# them to startplot(). +# +# Displaying voltages and currents on the same .TRAN graph won't work +# well because they currently have to have the same Y scale. This +# could be fixed by assigning carefully to $mmm in startplot(). +# +# This whole scheme is very clumsy. There should be a driver program +# with command-line option parsing. + +# This program and its documentation are free software; you can +# redistribute them and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# This program and its documentation are distributed in the hope that +# they will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +die if @ARGV; + +%facttimes= qw(f 1e-15 + p 1e-12 + n 1e-9 + u 1e-6 + m 1e-3 + K 1e3 + Meg 1e6 + G 1e9 + T 1e12); + +sub startplot () { + $logxy= 0; + for ($yn=1; $yn<=$#columns; $yn++) { + $mmm[$yn]= 'y'; + } + if ($kind eq 'Freq') { + $logxy= 1; + for ($yn=1; $yn<=$#columns; $yn++) { + die unless $columns[$yn] =~ m/.*([MP])\(\d+\)$/i; + $mmm[$yn]= 'y2' if uc $1 eq 'P'; + } + } + printf "S %s %d", $cplot, $logxy or die $!; + for ($yn=1; $yn<=$#columns; $yn++) { + printf " %s:%s", $mmm[$yn], $columns[$yn] or die $!; + } + print "\n" or die $!; +} +sub endplot () { + return unless defined $kind; + print "T\n" or die $!; +} + +$readahead= ; +for (;;) { + $linesofar= $readahead; + for (;;) { + $readahead= ; + last unless $readahead =~ s/^\+//; + die unless length $linesofar; + $linesofar =~ s/\n$//; + $linesofar .= $readahead; + } + $_= $linesofar; + last unless length; + s/\s+$//; + + if (m/^\#(\w+)/) { + endplot(); + $kind= $1; + @columns= split /\s+/; + $cplot= $kind; + startplot(); + next; + } elsif (!defined $kind) { + next; + } elsif (s/^\s+//) { + @numbers= split /\s+/; + map { + if (m/^(\-?\d+\.\d*)([A-Za-z]+)$/) { + die "factor $2" unless exists $facttimes{$2}; + $_= $1*$facttimes{$2}; + } + } @numbers; + print "D @numbers\n" or die $!; + } else { + die "$_ ?"; + } +} +die "no plots" unless defined $kind; +endplot(); +print "F\n" or die $!; + +# $Id: gnucap2genspic,v 1.4 2007-09-21 21:21:15 ianmdlvl Exp $ diff --git a/scripts/hexterm b/scripts/hexterm new file mode 100755 index 0000000..9a1b047 --- /dev/null +++ b/scripts/hexterm @@ -0,0 +1,311 @@ +#!/usr/bin/tclsh8.4 +set comment { +# +Use of the screen: +0 1 2 3 4 5 6 7 +xxxE hh hh hh hh hh hh hh hh hh hh hh hh hh hh hh hh_| abcd e_.. .... ...._| +} +# Display: +# | is a vertical delimiter +# E is either | to mean echo is on or ' to mean it is off +# hh are hex digits of output: +# 00-ff actual hex data (bold for stuff we entered) +# 0-f under cursor: one digit entered, need the next +# abcde_.... are ASCII output: +# . things we can't print including SPC and _ +# in both, we may see +# space we haven't yet filled +# _ cursor when in other tab +# xxx number of bytes read/written so far +# Keystrokes: +# TAB switch between hex and literal mode +# ^C, ^D quit +# ^Z suspend +# Keystrokes in hex mode only: +# RET move to a new line; if already at start of line, +# set count to 0 +# DEL clear any entered hex digit +# SPC send 00 +# ' toggle echo +# nyi: +# G-Z record last bytes we transmitted and store in memory +# if we were halfway through a hex byte, first digit +# is length of string to record +# g-z play back memory + + +# Copyright 2005 Ian Jackson +# +# This script and its documentation (if any) are free software; you +# can redistribute it and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# chiark-named-conf and its manpage are distributed in the hope that +# it will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + + +if {[llength $argv] != 1} { error "need serial port arg" } + +set port [lindex $argv 0] + +set count 0 +set lit 0 ;# 1 means literal (ASCII) entry mode +set echo 1 + +proc p {s} { + puts -nonewline $s +} + +proc tput {args} { + global tput + if {[catch { set s $tput($args) }]} { + set s [eval exec tput $args] + set tput($args) $s + } + p $s +} + +proc csr_pos {lit bytenum} { + set x [expr { + (!$lit ? (3*$bytenum) : 53+$bytenum) + + ($bytenum>>2) - (2-$lit)*($bytenum==16) + + 5 + }] + tput hpa $x +} + +proc csr_this {} { global lit x; csr_pos $lit $x } +proc csr_other {} { global lit x; csr_pos [expr {!$lit}] $x } +proc csrs_erase {} { csr_this; p " "; csr_other; p " " } +proc csr_this_show {} { + global h1 + csr_this; if {[info exists h1]} { p $h1; p "\b" } +} +proc csrs_show {} { + csr_other; p _ + csr_this_show +} + +proc echop {} { + global echo + return [expr {$echo ? "|" : "'"}] +} + +proc newline {} { + global x echo count + if {[info exists x]} { csrs_erase; p "\r\n" } + set x 0 + p [format "%3x%s%*s|%*s|" $count [echop] 52 "" 21 ""] + csrs_show +} + +proc p_ch_spaces {} { + global x lit + if {$x==15} return + if {$lit} { p " " } + if {($x & 3) != 3} return + p " " +} + +proc p_rmso {smso} { + if {[string length $smso]} { tput sgr0 } +} + +proc ch {d smso} { + global lit x count + if {$x == 16} newline + if {[string length $smso]} { tput $smso } + set h [format %02x [expr {$d & 0xff}]] + set c [format %c [expr {($d > 33 && $d < 127 && $d != 95) ? $d : 46}]] + if {$lit} { + p $c; csr_other; p $h + p_ch_spaces + p_rmso $smso + p _ + } else { + p $h; csr_other; p $c + p_ch_spaces + p_rmso $smso + p _ + } + incr x + set count [expr {($count+1) & 0xfff}] + csr_this_show +} + +proc onreadp {} { + global p + while 1 { + set c [read $p 1] + binary scan $c c* d + if {![llength $d]} { + if {[eof $p]} { error "eof on device" } + return + } + ch $d {} + } +} + +proc transmit {d} { + global p echo + puts -nonewline $p [format %c $d] + if {$echo} { ch $d bold } +} + +proc k_echo {} { + global echo + set echo [expr {!$echo}] + tput hpa 3 + p [echop] + csr_this +} + +proc k_newline {} { + global count x + if {$x} { + newline + } else { + set count 0 + p "\r" + p [format %3x $count] + csr_this + } +} + +proc k_switch {} { + global lit h1 + csrs_erase + catch { unset h1 } + set lit [expr {!$lit}] + csrs_show +} + +proc k_stop {} { + restore + exit 0 +} + +proc k_suspend {} { + restore + exec kill -TSTP [info pid] + setup +} + +proc k_noparthex {} { + global h1 + csrs_erase + catch { unset h1 } + csrs_show +} + +proc k_hexdigit {c} { + global h1 echo + if {![info exists h1]} { set h1 $c; p $c; p "\b"; return } + set d [expr 0x${h1}${c}] + unset h1 + transmit $d + if {!$echo} { p " \b" } +} + +proc onreadk {} { + global lit + while 1 { + set c [read stdin 1] + binary scan $c c* d + if {![llength $d]} { + if {[eof stdin]} { error "eof on stdin" } + return + } + switch -exact $d { + 9 { k_switch; continue } + 3 - 4 { k_stop; continue } + 26 { k_suspend; continue } + } + if {$lit} { transmit $d; continue } + switch -exact $d { + 13 { k_newline; continue } + 32 { transmit 0; continue } + 39 { k_echo; continue } + 127 { k_noparthex; continue } + } + if {$d >= 48 && $d <= 57} { k_hexdigit $c; continue } + set kl [expr {$d | 32}] + if {$d >= 97 && $d <= 102} { k_hexdigit $c; continue } + p "\a" + } +} + +proc try {script} { + if {[catch { uplevel 1 $script } emsg]} { + catch { puts stderr "(warning: $emsg)" } + } +} + +proc tryv {variable script} { + upvar #0 $variable var + if {![info exists var]} return + uplevel 1 " + global $variable + $script + " + unset var +} + +proc restore {} { + tryv x { puts "\r\n" } + try { fconfigure stdin -blocking true } + try { fconfigure stdout -blocking true } + tryv term_stty { exec stty $term_stty } + tryv p { close $p } +} + +proc setup {} { + global term_stty port p + + set term_stty [exec stty -g] + + set p [open $port {RDWR NONBLOCK} 0] + + exec stty min 1 time 0 -istrip -ocrnl -onlcr -onocr -opost \ + -ctlecho -echo -echoe -echok -echonl -iexten -isig \ + -icanon -icrnl + exec stty -F $port min 1 time 0 -istrip -ocrnl -onlcr -onocr -opost \ + -ctlecho -echo -echoe -echok -echonl -iexten -isig \ + -icanon -icrnl \ + 9600 clocal cread -crtscts -hup -parenb cs8 -cstopb \ + -ixoff bs0 cr0 ff0 nl0 -ofill -olcuc + + fconfigure $p -blocking false -buffering none -encoding binary \ + -translation binary + + fconfigure stdin -blocking false -buffering none -translation binary + fconfigure stdout -blocking false -buffering none -translation binary + + newline + + fileevent stdin readable onreadk + fileevent $p readable onreadp +} + +proc bgerror {m} { + try { + restore + global errorInfo errorCode + puts stderr "$m\n$errorCode\n$errorInfo" + } + exit 127 +} + +if {[catch setup emsg]} { + restore + error $emsg $errorInfo $errorCode +} + +vwait quit diff --git a/scripts/named-conf b/scripts/named-conf new file mode 100755 index 0000000..120f2b7 --- /dev/null +++ b/scripts/named-conf @@ -0,0 +1,1175 @@ +#!/usr/bin/perl -w +# This is chiark-named-conf, which is Copyright 2002 Ian Jackson. +# +# chiark-named-conf and its manpage are free software; you can +# redistribute it and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# chiark-named-conf and its manpage are distributed in the hope that +# it will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +use strict; +use IO::File; +use Data::Dumper; +use POSIX; +use Fcntl qw(:DEFAULT :flock); + +# bastard Perl wants me to do this now ! +sub loarg(); +sub soarg(); +sub usageerr ($); +sub cfg_fail ($); +sub read_config ($); +sub qualify ($); +sub bad_modifiers ($); +sub zone_conf ($$$$$@); +sub set_output($); +sub progress ($$); +sub verbose ($); +sub process_zones (@); +sub zone_warning ($$); +sub zone_warnmore ($); +sub zone_check_full (); +sub zone_reset(); +sub zone_investigate(); +sub zone_check_nsrrset ($$$$); +sub zone_ns_name ($$); +sub zone_server_queue ($$$$$); +sub zone_server_addr ($$$$$); +sub zone_check_soa ($$$$); +sub zone_consistency(); +sub zone_servers_ok (); +sub zone_consistency_set ($%); +sub zone_check_local (); +sub zone_servers_simplefind (); +sub zone_server_simple ($$$); +sub zone_style ($$); +sub mail_zone_before (); +sub mail_zone_after (); +sub pmail ($); +sub ptime ($); +sub mail_zone_mail (); +sub zone_output (); +sub output_files (); +sub debug_dump ($); +sub debug_trace ($); +sub has_suffix_of ($$); +sub lookup ($$$$); +sub dig (&$$$); +sub domain_canon ($$); + +use vars qw($quis $stdout_fh $stderr_fh + $mode $doall $domail + $etcfile $where + $debug $needglue $localonly $repeat $verbosity + $admin $mail_state_dir $mail_max_warnfreq + $progress_fh $warn_fh $modifiers + %group2modcmd %group2used); + +$|=1; +$quis= $0; $quis =~ s,.*/,,; + +$mode= ''; +$doall= 0; +$etcfile= "/etc/bind/chiark-conf-gen.zones"; +$where= ''; +$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 <... +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) + ... 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 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 < +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 <getline); + $/= "\n"; + (!$log->error and $log->close) + or die "$quis: reread or close ${m_base}_log: $!\n"; + $log->eof or die; + $m_m->close or die "$quis: close ${m_base}_mail: $!\n"; + $m_m= new IO::File "${m_base}_mail" + or die "$quis: reopen ${m_base}_mail: $!"; + + defined($c= fork) or die "$quis: fork for mail: $!\n"; + if (!$c) { + defined dup2($m_m->fileno, 0) + or die "$quis - sendmail: dup for stdin: $!\n"; + exec (qw(/usr/sbin/sendmail -odb -oee -oi), + ($domail eq 'final' ? '-t' : $$cfg{'admin'})); + die "$quis - sendmail: exec: $!\n"; + } + $m_m->close; + $!=0; $r= waitpid $c,0; + $r == $c or die "$quis: waitpid sendmail ($c): $r $!"; + $? and warn "$quis: sendmail failed: $?\n"; + + printf " %-40s %s\n", $zone, + length $zone_to ? $zone_to : 'reporting to admin' + or die "$quis: write mailing report: $!\n"; +} + +#-------------------- outputting + +sub zone_output () { + my ($o,$m); + + $o= "zone \"$zone\" {\n"; + if ($$cfg{'s'} =~ m/p/) { + $o.= " type master;\n"; + } else { + $o.= " type slave;\n". + " masters {\n"; + foreach $m (@{ $$cfg{'servers'} }) { $o.= " $m;\n"; } + $o.= " };\n"; + } + $o.= " file \"$$cfg{'file'}\";\n"; + $o.= "};\n"; + return $o; +} + +sub output_files () { + my ($fn,$ofn,$mfn,$l,$dir, $maxmode,$h,@to_install); + + foreach $ofn (keys %output_contents) { + $fn= $ofn; $mfn= "output file $fn"; + for (;;) { + if (!lstat $fn) { + $! == &ENOENT or die "$quis: stat $mfn:\n $!\n"; + $maxmode= 0666; + last; + } elsif (-f _) { + $maxmode= (stat _)[2]; + last; + } elsif (-l _) { + defined($l= readlink $fn) + or die "$quis: readlink $mfn:\n $!\n"; + $dir= $fn =~ m,^.*/, ? $& : './'; + $fn= "$dir$l" unless $l =~ m,^/,; + $mfn= "output file $fn (symlink target of $ofn)"; + } else { + die "$quis: output file $mfn exists but is not a file". + " (or symlink to one)"; + } + } + unlink "$fn.new" or $! == &ENOENT or + die "$quis: cannot clear out old .new version of $mfn:\n $!"; + $h= new IO::File "$fn.new",'w',$maxmode + or die("$quis: create .new version of $mfn:\n $!"); + print $h + "# generated by $quis, do not edit\n", + $output_contents{$ofn} + or die "$quis: write data to .new version of $mfn:\n $!"; + $h->close + or die "$quis: close .new version of $mfn:\n $!"; + push @to_install, $fn,$mfn; + } + + while (($fn,$mfn, @to_install) = @to_install) { + rename "$fn.new",$fn + or die "$quis: install new version of $mfn:\n $!"; + } +} + +#-------------------- general utilities + +sub debug_dump ($) { + my ($vn); + return unless $debug>1; + local $Data::Dumper::Terse=1; + foreach $vn (split /\s+/, $_[0]) { + print "$vn := ", eval "Dumper(\\$vn)"; + } +} + +sub debug_trace ($) { + return unless $debug; + print "D $_[0]\n"; +} + +sub has_suffix_of ($$) { + my ($whole,$suffix) = @_; + debug_trace("has_suffix_of $whole $suffix"); + return 0 if length $whole < length $suffix; + return 0 if substr($whole, length($whole) - length($suffix)) ne $suffix; + debug_trace("has_suffix_of $whole $suffix YES"); + return 1; +} + +sub lookup ($$$$) { + my ($domain,$type,$okrcodes,$w) = @_; + my ($c,$h,@result); + debug_trace("lookup ==> (->$okrcodes) $domain $type"); + $h= new IO::Handle; + + defined($c= open $h, "-|") or die "$quis: fork adnshost:\n $!\n"; + if (!$c) { + exec 'adnshost','-Fi','+Do','+Dt','+Dc','-Cf',"-t$type", + '-',"$domain"; + die "$quis: exec adnshost:\n $!\n"; + } + @result= $h->getlines(); + $h->error and die "$quis: read from adnshost:\n $!\n"; + chomp @result; + $!=0; $h->close; + die "$quis: lookup -t$type $domain $okrcodes ($w) failed $? $! @result\n" + if $! or $?&255 or $?>1536 or index($okrcodes,$?>>8)<0; + debug_trace("lookup <== $? @result"); + return ($?,@result); +} + +sub dig (&$$$) { + my ($eachrr, $qowner,$qtype,$qaddr) = @_; + # also pseudo-rr with type `flags:' + my ($h,$inmid,$irdata,$c,$digwhy); + local ($_); + + debug_trace("dig ==> \@$qaddr $qowner $qtype"); + + $h= new IO::Handle; + defined($c= open $h, "-|") or die "$quis: fork dig:\n $!\n"; + if (!$c) { + open STDERR, ">&STDOUT" or die $!; + exec ('dig', + '+nodef','+nosea','+norecurse', + "\@$qaddr",'-t',$qtype,$qowner); + die "$quis: exec dig:\n $!\n"; + } + $inmid=''; + for (;;) { + if (!defined($_= $h->getline())) { + $h->error() and die "$quis: read from dig:\n $!\n"; + last; + } + chomp; + if (length $inmid) { + s/^\s+/ / or die "$inmid // $_ ?"; + s/\;.*$//; + $_= $inmid.$_; + $inmid=''; + s/$/ \(/ unless s/\s*\)\s*$//; + } + if (s/\s*\(\s*$//) { $inmid= $_; next; } + $digwhy= "dig $qowner $qtype $qaddr \`$_'"; + if (m/^\;\; flags\:( [-0-9a-z ]+)\;/) { + $dig_owner=''; $dig_type='flags:'; $dig_rdata= "$1 "; + debug_trace("dig f: $dig_rdata"); + &$eachrr; + } elsif (m/^\;/) { + } elsif (!m/\S/) { + } elsif (m/^([-.0-9a-z]+)\s+\d\w+\s+in\s+([a-z]+)\s+(\S.*)/i) { + $dig_owner=domain_canon($1,$digwhy); $dig_type=lc $2; $irdata=$3; + if ($dig_type eq 'a') { + $irdata =~ m/^[.0-9]+$/ or die "$irdata ?"; + $dig_rdata= $&; + } elsif ($dig_type eq 'ns') { + $irdata =~ m/^[-.0-9a-z]+$/i or die "bad nameserver $irdata ?"; + $dig_rdata= domain_canon($irdata,$digwhy); + } elsif ($dig_type eq 'soa') { + $irdata =~ m/^([-.0-9a-z]+)\s+.*\s+(\d+)(?:\s+\d\w+){4}$/i + or die "bad SOA $irdata ?"; + $dig_rdata= $2.' '.domain_canon($1,$digwhy); + } else { + debug_trace("ignoring uknown RR type $dig_type"); + next; + } + debug_trace("dig $dig_owner $dig_type $dig_rdata"); + &$eachrr; + } else { + debug_trace("ignoring unknown dig output $_"); + } + } + $h->close; + debug_trace("dig <== gave $?"); +} + +sub domain_canon ($$) { + my ($i,$w) = @_; + $i =~ s/(.)\.$/$1/; + return '.' if $i eq '.'; + die "domain $i ($w) ?" unless $i =~ m/^[0-9a-z]/i; + return lc $i; +} diff --git a/scripts/named-conf.8 b/scripts/named-conf.8 new file mode 100644 index 0000000..0faae0a --- /dev/null +++ b/scripts/named-conf.8 @@ -0,0 +1,596 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.TH CHIARK\-NAMED\-CONF 8 "12th January 2002" "Greenend" "chiark utilities" +.SH NAME +chiark\-named\-conf \- check and generate nameserver configuration +.SH SYNOPSIS +.BR chiark\-named\-conf " [\fIoptions\fP] " \-n | \-y | \-f +.br +\fBchiark\-named\-conf\fP [\fIoptions\fP] \fIzone ...\fP +.SH DESCRIPTION +.B chiark\-named\-conf +is a tool for managing nameserver configurations and checking for +suspected DNS problems. Its main functions are to check that +delegations are appropriate and working, that secondary zones are +slaved from the right places, and to generate a configuration for +.BR BIND , +from its own input file. + +By default, for each zone, in addition to any warnings, the output +lists the zone's configuration type. If the zone is checked, the +serial number at each of the nameservers is shown, with any +unpublished primary having +.B * +after the serial number. +.SH OPTIONS + +.SS MODE OPTIONS +If one of the options +.BR -n ", " -y ", or " -f +is supplied then chiark-named-conf will read its main configuration +file for the list of relevant zones. It will then check the +configuration and delegation for each zone +and/or generate and install a new configuration file for +the nameserver: +.TP +.BR \-y | \-\-yes +Generate and install new nameserver config, as well as checking +configuration, for all listed zones. +.TP +.BR \-n | \-\-no +Check configuration, for all listed zones, but +do not generate new nameserver config. +.TP +.BR \-f | \-\-force +Generate and install new nameserver config, without doing any +configuration cross-checking. (Syntax errors in our input +configuration will still abort this operation.) +.TP +.BR \-\-nothing +Do nothing: do no checks, and don't write a new config. This can be +used to get a list of the zones being processed. +.TP +.BR \-\-mail\-first " | " \-\-mail\-middle " | " \-\-mail\-final +Send mails to zone SOA MNAMEs reporting zones with problems. You must +call chiark\-named\-conf at least twice, once with \-\-mail\-first, +and later with \-\-mail\-final, and preferably with one or more calls +to \-\-mail\-middle in between. All three options carry out a check +and store the results; \-\-mail\-final also sends a mail to the zone +SOA MNAME or local administrator, if too many of the calls had errors +or warnings (calls before the most recent \-\-mail\-first being +ignored). +.TP +.B \-mail\-final\-test +just like \-\-mail\-final except that it always sends mail to the +local server admin and never to remote zone contacts, adding +.B (testing!) +to the start of the To: field. +.LP +Alternatively, one or more zone names may be supplied as arguments, in +which case their delegations will be checked, and compared with the +data for that zone in the main configuration (if any). In this case +no new configuration file for the nameserver will be made. + +.SS ADDITIONAL OPTIONS +.TP +.BR \-A | \-\-all +Checks even zones known to be broken. Ie, ignores the +.B ? +zone style modifier in the configuration. +.TP +.BR \-C | \-\-config " \fIconfig\-file\fP" +Use +.I config\-file +instead of +.BR /etc/bind/chiark-conf-gen.zones . +Also changes the default directory. +.TP +.BR \-D +Enables debugging. Useful for debugging chiark\-named\-conf, but +probably not useful for debugging your DNS configuration. Repeat to +increase the debugging level. (Maximum is +.BR -DD .) +.TP +.BR \-g | \-\-glueless +Do not warn about glueless referrals (strictly, makes the zone style +modifier +.B ~ +the default). Not recommended - see the section GLUELESSNESS, below. +.TP +.BR \-l | \-\-local +Only checks for mistakes which are the responsibility of the local +administrator (to fix or get fixed). This means that for published +and stealth zones we only check that we're slaving from the right +place and that any names and addresses for ourself are right. For +primary zones all checks are still done. It is a mistake to specify +.B \-l +with foreign zones (zones supplied explictly on the command line but +not relevant to the local server); doing so produces a warning. +.TP +.BI \-m group !*$@~? +Overrides a +.B modifiers +directive in the configuration file. The modifiers specified in the +directive are completely replaced by those specified in this command +line option. (Note that modifiers specified in per-zone directives +still override these per-group settings.) If more than one +.B modifiers +directive specifies the same group, they are all affected. +.B modifiers +directives which don't specify a group cannot be affected. It is an +error if the group does not appear in the config file. See ZONE STYLE +MODIFIERS, below. +.PP +The special group +.B foreign +is used for zones which don't appear in the configuration file. +.TP +.BR \-q | \-\-quiet +Suppress the usual report of the list of nameservers for each zone and +the serial number from each. When specified twice, do not print any +information except warnings. +.TP +.BR \-r | \-\-repeat +When a problem is detected, warn for all sources of the same imperfect +data, rather than only the first we come across +.TP +.BR \-v | \-\-verbose +Print additional information about what is being checked, as we go +along. +.SH USAGE +The file +.B /etc/bind/chiark-conf-gen.zones +(or other file specified with the +.B \-C +option) contains a sequence of directives, one per line. Blank lines +are permitted. Leading and trailing whitespace on each line is +ignored. Comments are lines starting with +.BR # . +Ending a line with a +.BR \\ +joins it to the next line, so that long directives can be split across +several physical lines. +.SS GENERAL DIRECTIVES +These directives specify general configuration details. They should +appear before directives specifying zones, as each will affect only +later zone directives. Foreign zones (zones explicitly specified on +the command line but not mentioned in the configuration) use the +configuration settings prevailing at the end of the config file. +.TP +\fBadmin\fP \fIemail\-address\fP +Specifies the email address of the local administrator. This is used +in the From: line of mails sent out, and will also receive copies of +the reports. There is no default. +.TP +\fBdefault\-dir\fP \fIdirectory\fP +Makes +.I directory +be the default directory (which affects the interpretation of +relative filenames). The default is the directory containing +the main configuration file, ie +.BR /etc/bind +if no +.B -C +option is specified. +.TP +\fBforbid\-addr\fP [\fIip-address ...\fP] +Specifies the list of addresses that are forbidden as any nameserver +for any zone. The default is no such addresses. +.TP +\fBforbid\-addr\fP [\fIip-address ...\fP] +Specifies the list of addresses that are forbidden as a nameserver +for a zone for which we are the primary - ie, the list of our old or +to-be-obsoleted slaves. The default is no such addresses. +.TP +\fBserverless\-glueless\fP \fIdomain ...\fP +Specifies a list of domains under which we do not expect to find any +nameservers without glue; for these zones it is OK to find glueless +referrals. +Each domain listed names a complete subtree of the DNS, starting at +the named point. The default is +.BR "in\-addr.arpa ip6.arpa ip6.int" . + +To avoid indefinitely long or even circularly glueless referrals +(which delay or prevent lookups) it is necessary for all sites to +effectively implement similar conventions; currently the author +believes that only the reverse lookup namespaces are conventionally +devoid of (glueless) nameservers, and therefore fine to provide +glueless referrals for. See GLUELESSNESS below. +.TP +\fBallow-\-indirect\-glue\fP \fInameserver-superdomain ...\fP +Specifies a list of domains under which we expect to find glueless +nameservers, with up to one layer of indirection. +For nameservers under these domains it is OK to to find glueless +referrals, but only when listed as a nameserver for a zone which is +not itself a subdomain of an \fBallow-indirect-glue\fR +\fInameserver-superdomain\fR. + +This supports to common configuration style where DNS operator(s) set +up all of their nameservers with names within a small subsection of +the DNS (the portions under \fInameserver-superdomain\fRs), and +provide glueless referrals naming these nameservers for all other +zones. This provides at most one level of missing glue. + +Note that if the DNS administrators collectively able to influence the +service for some zone (including the admins for its superzones, the +zones containing its nameservers, and their superzones and so forth) +are not in sufficiently close communication do not all agree on the +proper set of \fInameserver-superdomain\fR then they might still set +up circular glue and \fBchiark-named-conf\fR would not necessarily be +able to detect this even if it was run on every relevant nameserver. +.TP +\fBmail\-state\-dir\fP \fIdirectory\fP +Uses +.I directory +for storing information about recent failures for mailing to zone +admins. See \-\-mail\-first et al. Old files in here should be +cleaned up periodically out of cron. There is no default. +.TP +\fBmail\-max\-warnfreq\fP \fIpercentage\fP +When \-\-mail\-final is used, a mail will be sent to all zones which +had warnings or errors more than +.IR percentage % +of the times \-\-mail\-* was used (since the last \-\-mail\-first). +The default is 50%. +.TP +.BR modifiers " " !*$@~? "] [\fIgroup\fP]" +Applies the specified zone style modifiers (see below) to subsequently +declared zones (until the next +.B modifiers +directive), as if the modifiers specified were written out for +each zone. You must specify at least one character for the modifiers; +if you want to reset everything to the default, just say +.BR ! . +If style modifiers specified in the zone directive +conflict with the +.B modifiers +directive, those specified in the zone directive take effect. +.I group +may contain alphanumerics and underscores, and is used for the +.B -m +command-line option. +.TP +\fBself\-addr\fP \fIip-address ...\fP +Specifies the list of addresses that this server may be known by in +A records. There is no default. +.TP +\fBoutput\fP \fIformat\fP \fIfilename\fP [\fIformat\fP \fIfilename ...\fP] +Arranges that each +.I filename +will be overwritten when +.BR -y " or " -f +are used; its new contents will be configuration +directives for the zones which follow for the +nameserver in question. Currently the only +.I format +supported is +.B bind8 +which indicates new-style BIND 8. If no zones follow, then each +file will still be overwritten, by an effectively empty file. +Default: if there is no +.B output +directive in the configuration then the default is to use +.BR bind8 " " chiark-conf-gen.bind8 ; +otherwise it is an error for there to be any zones in the +configuration before the first +.B output +directive. +.TP +\fBself\-ns\fP \fIfqdn ...\fP +Specifies the list of names that this server may be known by in NS +records. There is no default. Any trailing * is replaced by the name +of the zone being checked, so for example +.B self\-ns isp.ns.* +before the zone example.com would mean to expect us to be listed as +isp.ns.example.com +in the NS RRset. +.TP +\fBself\-soa\fP \fIfqdn ...\fP +Specifies the list of names that this server may be known by in +the ORIGIN field of SOA records. There is no default. Any trailing +* is replaced by the name of the zone, as for +.BR self\-ns . +.TP +.BI self " fqdn ..." +Equivalent to both +.B self\-ns " and " self\-soa +with the same set of names. +.TP +\fBslave\-dir\fP \fIdirectory\fP [[\fIprefix\fP] \fIsuffix\fP] +Specifies the directory in which slave (published and stealth) +zonefiles should be placed. The default +.I directory +is +.BR /var/cache/bind/chiark-slave . +The default +.IR suffix " and " prefix +are empty; they also will be reset to these defaults by a +.B slave\-dir +directive which does not specify them. +.SS ZONE DIRECTIVES +These directives specify one or more zones. +.TP +.BR primary [ !*$@~? "] \fIzone filename\fP" +Specifies that this server is supposed to be the primary nameserver +for +.I zone +and that the zone data is to be found in +.IR filename . +.TP +.BR primary\-dir [ !*$@~? "] \fIdirectory\fP[" / "\fIprefix\fP] [\fIsuffix\fP[" / \fIsubfile\fP]] +Search +.I directory +for files whose names start with +.I prefix +and end with +.IR suffix . +Each such file is taken to represent a zone file for which this server +is supposed to be the primary; the part of the filename between +.IR prefix " and " suffix +is the name of the zone. + +If +.BI / subfile +is specified, then instead of looking for files, we search for +directories containing +.IR subfile ; +directories which do not contain the subfile are simply skipped. + +If +.IR directory [\fB/\fP prefix ] +exists as specified and is a directory then it is interpreted as +.I directory +with an empty prefix; otherwise the final path component is assumed to +be the prefix. If no +.IB suffix / subfile +is specified then the default is +.BR _db . +.TP +.BR published [ !*$@~? "] \fIzone origin\-addr\fP" +Specifies that this server is supposed to be a published slave +nameserver for the zone in question. +.TP +.BR stealth [ !*$@~? "] \fIzone server\-addr ...\fP" +Specifies that this server is supposed to be an unpublished secondary +(aka stealth secondary) for the zone in question. +.SS ZONE STYLE MODIFIERS +Each of the zone directives may optionally be followed by one or more +of the following characters (each at most once): +.TP +.B ! +Reverses the meaning of all style modifiers after the +.BR ! . +Only one +.BR ! +must appear in the modifier list. In this list, other modifiers which +default to `enabled' are described by describing the effect of their +inverse - see the description for +.B !@ +below. +.TP +.B * +Indicates that the zone is unofficial, ie that it is not delegated as +part of the global Internet DNS and that no attempt should be made to +find the superzone and check delegations. Note that unofficial, local +zones should be created with caution. They should be in parts of the +namespace which are reserved for private use, or belong to the actual +zone maintainer. +.TP +.B $ +Indicates that any mails should be sent about the zone to the +nameserver admin rather than to the zone SOA MNAME. This is the +default unless we are supposedly a published server for the zone. +.TP +.B !@ +Indicates that no mails should be sent about the zone to anyone. +.TP +.B ~ +Indicates that the zone's delegation is known to be glueless, and that +lack of glue should not be flagged. Not recommended - see the section +GLUELESSNESS, below. +.TP +.B ? +Indicates that the zone is known to be broken and no checks should be +carried out on it, unless the +.B \-A +option is specified. +.SS OTHER DIRECTIVES +.TP +\fBinclude\fP \fIfile\fP +Reads +.I file +as if it were included here. +.TP +\fBend\fP +Ends processing of this file; any data beyond this point is ignored. +.SH CHECKS +chiark\-named\-conf makes the following checks: + +Delegations: Each delegation from a server for the superzone should +contain the same set of nameservers. None of the delegations should +lack glue. The glue addresses should be the same in each delegation, +and agree with the local default nameserver. + +Delegated servers: Each server mentioned in the delegation should have +the same SOA record (and obviously, should be authoritative). + +All published nameservers - including delegated servers and servers +named in the zone's nameserver set: All nameservers for the zone +should supply the same list of nameservers for the zone, and none of +this authority information should be glueless. All the glue should +always give the same addresses. + +Origin server's data: The set of nameservers in the origin server's +version of the zone should be a superset of those in the delegations. + +Our zone configuration: For primary zones, the SOA origin should be +one of the names specified with +.BR self\-soa " (or " self ). +For published zones, the address should be that of the SOA origin. +For stealth zones, the address should be that of the SOA origin or one +of the published nameservers. +.SH GLUELESSNESS +Glue is the name given for the addresses of nameservers which are +often supplied in a referral. In fact, it turns out that it is +important for the reliability and performance of the DNS that +referrals, in general, always come with glue. + +Firstly, glueless referrals usually cause extra delays looking up +names. BIND 8, when it receives a completely glueless referral and +does not have the nameservers' addresses in its cache, will start +queries for the nameserver addresses; but it will throw the original +client's question away, so that when these queries arrive, it won't +restart the query from where it left off. This means that the client +won't get its answer until it retries, typically at least 1 second +later - longer if you have more than one nameserver listed. Worse, if +the nameserver to which the glueless referral points is itself under +another glueless referral, another retry will be required. + +Even for better resolvers than BIND 8, long chains of glueless +referrals can cause performance and reliability problems, turning a +simple two or three query exchange into something needing more than a +dozen queries. + +Even worse, one might accidentally create a set of circularly glueless +referrals such as +.br +.B example.com NS ns0.example.net.uk +.br +.B example.com NS ns1.example.net.uk +.br +.B example.net.uk NS ns0.example.com +.br +.B example.net.uk NS ns1.example.com +.br +Here it is impossible to look up anything in either example.com or +example.net.uk. + +There are, as far as the author is aware, no generally agreed +conventions or standards for avoiding unreasonably long glueless +chains, or even circular glueless situations. The only way to +guarantee that things will work properly is therefore to always supply +glue. + +However, the situation is further complicated by the fact that many +implementations (including BIND 8.2.3, and many registry systems), +will refuse to accept glue RRs for delegations in a parent zonefile +unless they are under the parent's zone apex. In these cases it can +be necessary to create names for the child's nameservers which are +underneath the child's apex, so that the glue records are both in the +parent's bailiwick and obviously necessary. + +In the past, the `shared registry system' managing .com, .net and .org +did not allow a single IPv4 address to be used for more than one +nameserver name. However, at the time of writing (October 2002) this +problem seems to have been fixed, and the workaround I previously +recommended (creating a single name for your nameserver somewhere +in .com, .net or .org, and using that for all the delegations +from .com, .net and .org) should now be avoided. + +Finally, a note about `reverse' zones, such as those in in-addr.arpa: +It does not seem at all common practice to create nameservers in +in-addr.arpa zones (ie, no NS RRs seem to point into in-addr.arpa, +even those for in-addr.arpa zones). Current practice seems to be to +always use nameservers for in-addr.arpa which are in the normal, +forward, address space. If everyone sticks to the rule of always +publishing nameservers names in the `main' part of the namespace, and +publishing glue for them, there is no chance of anything longer than a +1-step glueless chain might occur for a in-addr.arpa zone. It is +probably best to maintain this as the status quo, despite the +performance problem this implies for BIND 8 caches. This is what the +serverless\-glueless directive is for. + +Dan Bernstein has some information and examples about this at +.UR http://cr.yp.to/djbdns/notes.html#gluelessness +http://cr.yp.to/djbdns/notes.html#gluelessness +.UE +but be warned that it is rather opinionated. +.SS GLUELESSNESS SUMMARY + +I recommend that every nameserver should have its own name in every +forward zone that it serves. For example: +.br +.B zone.example.com NS servus.ns.example.com +.br +.B servus.ns.example.com A 127.0.0.2 +.br +.B 2.0.0.127.in-addr.arpa PTR servus.example.net +.br +.B servus.example.net A 127.0.0.2 +.LP +Domain names in +.B in-addr.arpa +should not be used in the right hand side of NS records. +.SH SECURITY +chiark\-named\-conf is supposed to be resistant to malicious data in +the DNS. It is not resistant to malicious data in its own options, +configuration file or environment. It is not supposed to read its +stdin, but is not guaranteed to be safe if stdin is dangerous. +.LP +Killing chiark-named-conf suddenly should be safe, even with +.BR -y " or " -f +(though of course it may not complete its task if killed), provided +that only one invocation is made at once. +.LP +Slow remote nameservers will cause chiark-named-conf to take +excessively long. +.SH EXIT STATUS +.TP +.B 0 +All went well and there were no warnings. +.TP +any other +There were warnings or errors. +.SH FILES +.TP +.B /etc/bind/chiark-conf-gen.zones +Default input configuration file. (Override with +.BR -C .) +.TP +.B /etc/bind +Default directory. (Override with +.BR -C " or " default\-dir .) +.TP +.IB dir /chiark-conf-gen.bind8 +Default output file. +.TP +.B /var/cache/bind/chiark-slave +Default location for slave zones. +.SH ENVIRONMENT +.LP +Setting variables used by +.BR dig (1) +and +.BR adnshost (1) +will affect the operation of chiark\-named\-conf. +Avoid messing with these if possible. +.LP +.B PATH +is used to find subprograms such as +.BR dig " and " adnshost . +.SH BUGS +The determination of the parent zone for each zone to be checked, and +its nameservers, is done simply using the system default nameserver. + +The processing of output from +.B dig +is not very reliable or robust, but this is mainly the fault of dig. +This can lead to somewhat unhelpful error reporting for lookup +failures. +.SH AUTHOR +.B chiark\-named\-conf +and this manpage were written by Ian Jackson +. They are Copyright 2002 Ian Jackson. + +chiark\-named\-conf and this manpage are free software; you can +redistribute it and/or modify it under the terms of the GNU General +Public License as published by the Free Software Foundation; either +version 2, or (at your option) any later version. + +This is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with this program; if not, consult the Free Software Foundation's +website at www.fsf.org, or the GNU Project website at www.gnu.org. diff --git a/scripts/ngspice2genspic b/scripts/ngspice2genspic new file mode 100755 index 0000000..1d80d60 --- /dev/null +++ b/scripts/ngspice2genspic @@ -0,0 +1,95 @@ +#!/usr/bin/perl +# ngspice2genspic - Copyright 2004 Ian Jackson - see below +# +# Reads the output from ngspice and outputs a `genspic' file +# for use with genspic2gnuplot. Takes no arguments or options. +# +# Limitations +# +# Only frequency (.AC) and transient plots have been tested. +# +# Displaying voltages and currents on the same .TRAN graph won't work +# well because they currently have to have the same Y scale. +# +# This whole scheme is very clumsy. There should be a driver program +# with command-line option parsing. + +# This program and its documentation are free software; you can +# redistribute them and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# This program and its documentation are distributed in the hope that +# they will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +die if @ARGV; + +while () { + if (m/^Total CPU/) { + die if $mode ne ''; + $seentcpu=1; + } elsif (m/^Current dynamic memory/ || + m/^Dynamic memory limit/ || + m/^Level not specified/ || + m/^Doing analysis/ || + m/^CPU time since/ || + m/^Circuit\:/) { + die if length $mode; + } elsif (m/\f/) { + die if m/\S/; + } elsif (!m/\S/ && $mode eq InitTransSkip) { + $mode= ''; + } elsif (m/^Initial Transient/ && $mode eq '') { + $mode= InitTrans; + } elsif (m/^Node/ && $mode eq InitTrans) { + $mode= InitTransSkip; + } elsif (m/\w/ && $mode eq InitTransSkip) { + } elsif (m/^\-\-/ && length $mode) { + } elsif (m/^\s\s+(\w+) Analysis /) { + if ($mode eq '') { + $mode= Table; + $ctable= $1; + $dokey= 1; + } else { + $dokey= 0; + } + } elsif (m/^\s\s+/ && ($mode eq Table or !length $mode)) { + } elsif (m/^Index / && $mode eq Table) { + s/\s+$//; + ($index,$key,@nowheadings) = split /\s+/, $_; + $logxy= 0+($key eq 'frequency'); + } elsif (m/^\d+\s/ && $mode eq Table) { + ($index,$key,@nowcolumns) = split /\s+/, $_; + shift @nowcolumns if $key =~ s/\,$//; + $coldata[$index] .= " $key" if $dokey; + $coldata[$index] .= " @nowcolumns"; + if (!$index) { + foreach $c (@nowheadings) { + $h= 'y'; + $h= 'y2' if $c =~ m/^ph\(/; + push @columns, "$h:$c"; + } + } + } elsif (!m/\S/ && $mode eq Table) { + print "S $ctable $logxy @columns\n" or die $!; + foreach $l (@coldata) { printf "D%s\n", $l or die $!; } + print "T\n" or die $!; + $mode= ''; + @columns= (); + @coldata= (); + } elsif (!m/\S/) { + $mode= '' if $mode eq InitTransSkip; + } else { + die "$mode: $_ ?"; + } +} +die "no plots" unless defined $ctable; +print "F\n" or die $!; + +# $Id: ngspice2genspic,v 1.3 2007-09-21 21:21:15 ianmdlvl Exp $ diff --git a/scripts/nntpid b/scripts/nntpid new file mode 100755 index 0000000..e0e286a --- /dev/null +++ b/scripts/nntpid @@ -0,0 +1,72 @@ +#!/usr/bin/perl + +# Originally by Simon Tatham +# Modified by Richard Kettlewell, Colin Watson, Ian Jackson + +use ChiarkNNTP; + +our $verbose; +($verbose='STDERR', shift @ARGV) if $ARGV[0] eq "-v"; + +my $c = cnntp_connect($verbose); +$c->banner_reader(); + +our $code; + +# some servers require a GROUP before an ARTICLE command +$c->docmd("GROUP misc.misc"); + +if(@ARGV == 0) { + while(<>) { + s/(^\s+|\s+$)//gs; + lookup($_); + } +} else { + while (@ARGV) { + my $item = shift @ARGV; + if($item !~ /[\@:]/ and not defined $group) { + # maybe a bare group followed by an article number + die unless @ARGV; + my $number = shift @ARGV; + $item = "$item $number"; + } + lookup($item); + } +} + +$c->docmd("QUIT"); +close S; + +sub lookup { + my $mid = shift; + + if($mid !~ /\@/ and $mid =~ /^(.*)[: ](\d+)$/) { + my ($g, $n) = ($1, $2); + $c->docmd("GROUP $g"); + $c->docmd("ARTICLE $n"); + } else { + $mid =~ s/.*\.*//; + $c->docmd("ARTICLE <$mid>"); + } + + my $fh= 'STDOUT'; + if (-t $fh) { + my $lesscmd= $ENV{'NNTPID_PAGER'}; + $lesscmd= 'less' unless defined $lesscmd; + open LESS, "|-", 'sh','-c',$lesscmd or die $!; + $fh= 'LESS'; + } + + while (1) { + ($code,$_) = $c->getline(); + s/[\r\n]//g; + last if /^\.$/; + s/^\.//; + print $fh "$_\n"; + } + + if ($fh ne 'STDOUT') { + close $fh or die "$? $!"; + } +} diff --git a/scripts/palm-datebook-reminders b/scripts/palm-datebook-reminders new file mode 100755 index 0000000..2d81e46 --- /dev/null +++ b/scripts/palm-datebook-reminders @@ -0,0 +1,156 @@ +#!/usr/bin/perl + +# Copyright 2003 Ian Jackson +# +# This script and its documentation (if any) are free software; you +# can redistribute it and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# chiark-named-conf and its manpage are distributed in the hope that +# it will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +use Palm::PDB; +use Palm::Datebook; +use Time::Local; +use POSIX; + +$us= $0 =~ m,[^/]+$, ? $& : $0; + +$detail= 86400*2; +$lookforward= 86400*(7*4+1); +# run at 15:00 daily + +sub setfilename($) { + die "$us: only one filename at a time please\n" if defined $filename; + $filename= $_[0]; +} + +while (@ARGV) { + $_= shift @ARGV; + if (m,^(?:\-t)?(\d+)/(\d+)$,) { + ($detail,$lookforward)=($1+0,$2+0); + } elsif (m,^[./],) { + setfilename($_); + } elsif (m/^\-f/) { + setfilename($'); + } else { + die "$us: unknown argument/option \`$_'\n"; + } +} + +$filename= "DatebookDB.pdb" if !defined $filename; + +defined($now= time) or die $!; +stat $filename or die "$us: $filename: $!\n"; +$backuptime= (stat _)[10]; + +$pdb = new Palm::PDB or die $!; +$pdb->Load($filename) or die $!; + + + +foreach $record (@{ $pdb->{records} }) { + if ($record->{start_hour} != 255) { + $timestr= sprintf("%02d:%02d-%02d:%02d", + $record->{start_hour}, + $record->{start_minute}, + $record->{end_hour}, + $record->{end_minute}); + @lt=(0, $record->{start_minute}, $record->{start_hour}); + } else { + @lt=(0, 0, 9); + $timestr= " "; + } + if ($record->{repeat}{type}) { + $timestr= "onwards "; + } + + push @lt, $record->{day}, $record->{month}-1, $record->{year}-1900; + defined($ettt= timelocal @lt) or die $!; + + next if ($ettt < $now - 86400 || + $ettt > $now + $lookforward); + + @lt2= localtime($ettt) or die $!; + defined($dowstr= strftime "%a", @lt2) or die $!; + + $datestr= sprintf("%04d-%02d-%02d", + $record->{year}, + $record->{month}, + $record->{day}); + + $timesortkey= "$datestr $timestr"; + $evhead= "$datestr $dowstr $timestr"; + + $desc= $record->{description}; + $desc =~ s/\x93/\~/g; + $desc =~ s/\xa3/L/g; + $desc =~ s/[^\n -\176]/ sprintf "\\x%02x", ord $& /eg; + $desc =~ s/^ +//; + $desc =~ s/\n+$//; + $desc .= "\n"; + + if ($desc =~ m/^(.{0,51})\n/) { + $descsumm= $1; + } elsif ($desc =~ m/^.{48}/) { + $descsumm= $&.'...'; + } else { + $descsumm= " --- no description ?! ---"; + } + $kind= $ettt < $now + $detail ? 'detail' : 'forward'; + + push @{ $events{$kind} }, { + TSK => $timesortkey, + Headline => sprintf("%s %s", $evhead, $descsumm), + Desc => $desc + }; +} + +sub sectline_detail(){ return "Imminent events"; } +sub sectline_forward(){ return "Forthcoming events"; } + +sub headline(){ + printf("%s\n", $ev->{Headline}) + or die $!; +} + +sub print_forward(){ headline(); } +sub print_detail(){ + print "\n" or die $!; + headline(); + $_= $ev->{Desc}; + s/^/ /gm; + print $_ or die $!; +} + +foreach $kind (qw(detail forward)) { + $sectline= &{"sectline_$kind"}; + printf("%s\n%s\n", + $sectline, + '-'x(length $sectline)) + or die $!; + if (!@{ $events{$kind} }) { + printf("None scheduled.\n") + or die $!; + } + foreach $ev (sort { $a->{TSK} cmp $b->{TSK} } @{ $events{$kind} }) { + &{"print_$kind"}; + } + print "\n" or die $!; +} + +@lt2= localtime $backuptime or die $!; +defined ($syncstr= strftime "%Y-%m-%d %a %H:%M", @lt2) or die $!; + +print("Date of last synch: $syncstr\n". + "Events entered on PDA after this date are omitted.\n") + or die $!; + +close STDOUT or die $!; diff --git a/scripts/palm-datebook-reminders.1 b/scripts/palm-datebook-reminders.1 new file mode 100644 index 0000000..40a9301 --- /dev/null +++ b/scripts/palm-datebook-reminders.1 @@ -0,0 +1,66 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.TH PALM\-DATEBOOK\-REMINDERS 1 "12th January 2002" "Greenend" "chiark utilities" +.SH NAME +palm\-datebook\-reminders \- generate simple report from Palm Datebook DB +.SH SYNOPSIS +.BR PALM\-DATEBOOK\-REMINDERS " [\fIoptions\fP] +.SH DESCRIPTION +.B palm\-datebook\-reminders +reads a Palm Datebook database and prints out a list of events in the +near future in a reasonably easy to read format. +.PP +Immiment events (ones in approximately the next 48 hours by default) +are listed with complete item text. Forthcoming events (approximately +the next 4 weeks by default) are listed in summary form. +.SH OPTIONS +.TP +.IR imminent \fB/\fR forthcoming " | " \fB-t\fR imminent \fB/\fR forthcoming +Specifies that imminent and forthcoming events are ones within the next +.I immiment +and +.I forthcoming +seconds, respectively. +.TP +.IR filename " | \fB-f\fR" filename +Reads the file +.I filename +instead of +.BR ./DatebookDB.pdb . +If the bare form is used, +.I filename +must start with a +.B . +or +.B / +(dot or slash). +.SH BUGS +The program is not configurable enough. + +The option parsing is very grotty. + +Proper character set conversion is not performed. +.SH FILES +.TP +.BR DatebookDB.pdb +Default input file. +.SH AUTHOR +.B PALM\-DATEBOOK\-REMINDERS +and this manpage were written by Ian Jackson +. They are Copyright 2003 Ian Jackson. + +palm\-datebook\-reminders and this manpage are free software; you can +redistribute it and/or modify it under the terms of the GNU General +Public License as published by the Free Software Foundation; either +version 2, or (at your option) any later version. + +This is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with this program; if not, consult the Free Software Foundation's +website at www.fsf.org, or the GNU Project website at www.gnu.org. + +Palm Datebook is a piece of proprietary software supplied with Palm +organisers. Palm and Datebook are probably trademarks. diff --git a/scripts/random-word b/scripts/random-word new file mode 100755 index 0000000..66d09a2 --- /dev/null +++ b/scripts/random-word @@ -0,0 +1,85 @@ +#!/usr/bin/perl + +# Copyright 2004 Ian Jackson +# +# This script and its documentation (if any) are free software; you +# can redistribute it and/or modify them under the terms of the GNU +# General Public License as published by the Free Software Foundation; +# either version 3, or (at your option) any later version. +# +# chiark-named-conf and its manpage are distributed in the hope that +# it will be useful, but WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +use IO::Handle; +use IO::File; +use POSIX; + +$want= 1; +$filename= "/usr/share/dict/words"; +@randfile= ("/dev/urandom", "/dev/random"); + +sub fail ($) { die "random-word: $_[0]\n"; } +open D, ">/dev/null" or fail("open /dev/null: $!"); + +while ($ARGV[0] =~ m/^\-/) { + $_= shift @ARGV; + if (m/^\-\-?$/) { + last; + } elsif (m/^\-n(\d+)$/) { + $want= $1; + } elsif (m/^\-f/ && length > 2) { + $filename= $'; + } elsif (m/^\-r/ && length > 2) { + @randfile= ($'); + } elsif (m/^\-D$/) { + open D, ">&STDERR" or fail("dup stderr for debug: $!"); + } else { + fail("unknown option \`$_'"); + } +} + +sub debug ($) { + print D "random-word: debug: $_[0]\n" + or fail("write debug: $!"); +} + +for $randfile (@randfile) { + $r= new IO::File "$randfile", 'r'; + debug("open $randfile ".($r ? "ok" : "failed $!")); + last if $r; + $!==&ENOENT or fail("cannot open $randfile: $!"); +} +$r or fail("could not open any random device: $!\n (tried @randfile)"); +$r->autoflush(0); + +$w= new IO::File $filename, 'r'; +$w or fail("cannot open $filename: $!"); +debug("open $filename ok"); +@words= <$w>; +$w->error and fail("cannot read $filename: $!"); +debug("read $filename ok"); + +while (@out < $want) { + $!=0; read $r,$rbytes,4; + length $rbytes==4 or fail("cannot read $randfile: $!"); + $wordno= unpack 'L',$rbytes; + $wordno &= ~0x80000000; + $wordno %= @words; + $_= $words[$wordno]; + chomp; + debug("picked $wordno \`$_'"); + next unless m/^([a-z][-a-z]+)$/; + push @out, $1; + debug("good, now ".scalar @out); +} + +debug("enough"); + +print join(' ',@out), "\n" + or fail("cannot write output: $!"); diff --git a/scripts/remountresizereiserfs b/scripts/remountresizereiserfs new file mode 100755 index 0000000..810a51e --- /dev/null +++ b/scripts/remountresizereiserfs @@ -0,0 +1,57 @@ +#!/bin/bash +# usage: +# remountresizereiserfs /mountpoint + +set -e +fail () { echo >&2 "$*"; exit 1; } +case "$#.$1" in 1.[^-]*);; *) fail 'bad usage';; esac + +mp=$1 + +df=`df -P $mp` +dfl2=`printf "%s" "$df" | sed 1d` + +case "$dfl2" in +/dev/*" "[0-9]*" "[0-9]*" "[0-9]*" "[0-9]*"% "/*) + dev=${dfl2%% *} + mp2=${dfl2##* } + if [ "x$mp2" != "x$mp" ]; then fail "mountpoint is $mp2 not $mp"; fi + ;; +*) fail "could not parse df output" ;; +esac + +dm=/dev/mapper +case "$dev" in +$dm/*/*) + fail "too many path segments in mapper device \`$dev'" + ;; +$dm/*) + lv=${dev#$dm/} + lv=${lv//--//} + case "$lv" in + *-*) ;; + *) fail "no single hyphen in mapper device \`$lv'";; + esac + vg=${lv%%-*} + lv=${lv#*-} + vg=${vg//\//-} + lv=${lv//\//-} + devu=/dev/$vg/$lv + ;; +*) + devu=$dev +esac + +lvi=$(lvdisplay -c $devu) +vg=${lvi#*:} +vg=${vg%%:*} +vgsz_kb=${lvi#*:*:*:*:*:*:} +vgsz_kb=${vgsz_kb%%:*} + +dbrfs=$(debugreiserfs $dev) +blksz_by=$(printf "%s" "$dbrfs" | egrep '^Blocksize: ' || fail "blocksize?") +blksz_by=${blksz_by#*: } + +vgsz_blk=$(dc -e "$vgsz_kb 1024* $blksz_by /p") + +echo mount -o remount,resize=$vgsz_blk $mp diff --git a/scripts/summarise-mailbox-preserving-privacy b/scripts/summarise-mailbox-preserving-privacy new file mode 100755 index 0000000..fd19090 --- /dev/null +++ b/scripts/summarise-mailbox-preserving-privacy @@ -0,0 +1,171 @@ +#!/usr/bin/perl +# usage: +# summarise-mailbox-preserving-privacy \ +# [] [--] +# our options: +# -S default: "summary of messages on " +# -F default: $HOME/.summarise-mailbox/last +# -f passed to from(1) must use -F if it contains / or | +# -s 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 +# +# 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= ) 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($_= )) { + 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= < +# +# This is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3, or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +CONFIG_CPPFLAGS= -DRWBUFFER_SIZE_MB=$(RWBUFFER_SIZE_MB) \ + -DREALLY_CHECK_FILE='"/etc/inittab"' + +CC= gcc +CFLAGS= $(WARNINGS) $(OPTIMISE) $(DEBUG) +CPPFLAGS= $(CONFIG_CPPFLAGS) + +WARNINGS= -Wall -Wwrite-strings -Wmissing-prototypes \ + -Wstrict-prototypes -Wpointer-arith -Wno-pointer-sign +OPTIMISE= -O2 +DEBUG= -g + +SYSTEM_GROUP= root + +prefix=/usr/local +etcdir=/etc +varlib=/var/lib + +confdir=$(etcdir)/$(us) +bindir=$(prefix)/bin +sbindir=$(prefix)/sbin +sharedir=$(prefix)/share/$(us) +txtdocdir=$(prefix)/share/doc/$(us) +exampledir=$(txtdocdir)/examples +vardir=$(varlib)/$(us) +mandir=${prefix}/man +man1dir=${mandir}/man1 +man8dir=${mandir}/man8 + +# INSTALL_PROGRAM_STRIP_OPT=-s + +INSTALL= install -c +INSTALL_SHARE= $(INSTALL) -m 644 -o root -g $(SYSTEM_GROUP) +INSTALL_SCRIPT= $(INSTALL) -m 755 -o root -g $(SYSTEM_GROUP) +INSTALL_PROGRAM= $(INSTALL_SCRIPT) $(INSTALL_PROGRAM_STRIP_OPT) +INSTALL_DIRECTORY= $(INSTALL) -m 2755 -o root -g $(SYSTEM_GROUP) -d diff --git a/sync-accounts/Makefile b/sync-accounts/Makefile new file mode 100644 index 0000000..f9279e2 --- /dev/null +++ b/sync-accounts/Makefile @@ -0,0 +1,66 @@ +# Makefile + +# This file is part of chiark-utils, a collection of useful programs +# used on chiark.greenend.org.uk. +# +# This file is: +# Copyright 2001-2002 Ian Jackson +# +# This is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3, or (at your option) any later version. +# +# This is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, consult the Free Software Foundation's +# website at www.fsf.org, or the GNU Project website at www.gnu.org. + +SYSTEM_GROUP= root + +INSTALL= install -c +INSTALL_SHARE= $(INSTALL) -m 644 -o root -g $(SYSTEM_GROUP) +INSTALL_SCRIPT= $(INSTALL) -m 755 -o root -g $(SYSTEM_GROUP) +INSTALL_DIRECTORY= $(INSTALL) -m 2755 -o root -g $(SYSTEM_GROUP) -d + +prefix=/usr/local +bindir=$(prefix)/bin +mandir=${prefix}/man +man5dir=${mandir}/man5 +man8dir=${mandir}/man8 +txtdocdir=$(prefix)/share/doc/sync-accounts +exampledir=$(txtdocdir)/examples + +SCRIPTS= sync-accounts sync-accounts-createuser grab-account +MANPAGES5= sync-accounts +MANPAGES8= sync-accounts sync-accounts-createuser grab-account +EXAMPLES= linux bsd + +all: + +install: + $(INSTALL_DIRECTORY) $(bindir) + set -e; for f in $(SCRIPTS); do \ + $(INSTALL_SCRIPT) $$f $(bindir)/$$f; done + +install-docs: + $(INSTALL_DIRECTORY) $(man5dir) $(man8dir) + set -e; for f in $(MANPAGES5); do \ + $(INSTALL_SHARE) $$f.5 $(man5dir)/$$f.5; done + set -e; for f in $(MANPAGES8); do \ + $(INSTALL_SHARE) $$f.8 $(man8dir)/$$f.8; done + +install-examples: + $(INSTALL_DIRECTORY) $(exampledir) + set -e; for e in $(EXAMPLES); do \ + $(INSTALL_SHARE) sync-accounts.example-$$e \ + $(exampledir)/sync-accounts.$$e; \ + done + +clean: + rm -f *~ ./#*# + +distclean realclean: clean diff --git a/sync-accounts/grab-account b/sync-accounts/grab-account new file mode 100755 index 0000000..8003215 --- /dev/null +++ b/sync-accounts/grab-account @@ -0,0 +1,64 @@ +#!/bin/sh +# This is part of sync-accounts, a tool for synchronising UN*X password data. +# +# sync-accounts is +# Copyright 1999-2000,2002 Ian Jackson +# 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 [] +creates an entry in /etc/sync-accounts, and runs sync-accounts +$Id: grab-account,v 1.5 2007-09-21 21:21:15 ianmdlvl Exp $' + exit 1 +fi + +lu="$1" +sh="$2" + +if [ $# -gt 2 ] +then + ru="$3" +else + ru="$1" +fi + +cf=/etc/sync-accounts + +if perl -ne 'exit 1 if m/^\s*user\s+'$lu'\s/;' <$cf +then + perl -pe ' + next unless m/^\s*host\s+'$sh'\s*$/...m/^host|^end/; + next unless m/^\s*addhere\s*$/; + next if $done++; + print "user '$lu'".("'$lu'" eq "'$ru'" ? "" : " remote='$ru'")."\n" + or die $!; + END { + print(STDERR "\`addhere'\'' line not found\n"), $?=1 if !$? && !$done; + } + ' $cf >$cf.new + mv -f $cf.new $cf +else + echo "entry already exists in $cf, leaving alone" +fi + +sync-accounts $sh diff --git a/sync-accounts/grab-account.8 b/sync-accounts/grab-account.8 new file mode 100644 index 0000000..e5822dc --- /dev/null +++ b/sync-accounts/grab-account.8 @@ -0,0 +1,77 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.TH GRAB\-ACCOUNT 8 "14th July 2002" "Greenend" "chiark utilities" +.SH NAME +grab\-account \- add new account synchronised to remote system +.SH SYNOPSIS +.BI "grab\-account " local\-user " " source " [" remote\-user ] +.SH DESCRIPTION +.B grab-account +reconfigures sync-accounts to start synchronising a specified local +user (which may not yet exist) from a specified remote system, and +then invokes sync-accounts once to synchronise from that source. + +.B /etc/sync-accounts/createuser +should contain a +.B addhere +line in the appropriate source section (ie, after +.BR host " \fIsource\fP)." +grab-account adds a +.br +.BR " user" " \fIlocal\-user\fP [" remote= "\fIremote\-user\fP]" +.br +directive just before +.B addhere +and runs +.BR sync-accounts " \fIsource\fP." +.SH EXIT STATUS +.TP +.B 0 +All went well. +.TP +any other +There were problems. +.SH FILES +.BR /etc/sync-accounts ; +See also +.BR sync-accounts (8). +.SH ENVIRONMENT +See +.BR sync-accounts (8). +.SH BUGS +There is no locking of +.B /etc/sync-accounts +so do not invoke grab-account from a script, or more than once at a +time by hand. Do not edit /etc/sync-accounts by hand and also +simultaneously run grab-account. + +The mechanism involving +.B addhere +is suboptimal. This should be done with an include feature in +sync-accounts, so that grab-account does not have to edit a +configuration file that really belongs to the sysadmin. +.SH AUTHOR +.B grab-account +and this manpage are part of the +.B sync-accounts +package which was written by Ian Jackson . +They are Copyright 1999-2000,2002 Ian Jackson +, and Copyright 2000-2001 nCipher +Corporation Ltd. + +The sync-accounts package is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 3, or (at +your option) any later version. + +This is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with this program; if not, consult the Free Software Foundation's +website at www.fsf.org, or the GNU Project website at www.gnu.org. +.SH SEE ALSO +.BR sync-accounts "(8), " +.BR sync-accounts "(5), " +.BR passwd "(5)" diff --git a/sync-accounts/sync-accounts b/sync-accounts/sync-accounts new file mode 100755 index 0000000..cef131c --- /dev/null +++ b/sync-accounts/sync-accounts @@ -0,0 +1,677 @@ +#!/usr/bin/perl +# This is part of sync-accounts, a tool for synchronising UN*X password data. +# +# sync-accounts is +# Copyright 1999-2000,2002 Ian Jackson +# 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 () { + 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 () { + 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 "" 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= ; + 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 () { + chomp; + s/^\s*//; s/\s*$//; + next if m/^\#/ || !m/\S/; + finish() if m/^end$/; + if (m/^host\s+(\S+)$/) { + $ch_name= $1; + $ch_getpasswd= $ch_getgroup= $ch_getshadow= ''; + $ch_fetchedpasswd= $ch_fetchedgroup; + if (!@ARGV) { + $ch_doinghost= 1; + } elsif (exists $wanthost{$ch_name}) { + $wanthost{$ch_name}= 0; + $ch_doinghost= 1; + } else { + $ch_doinghost= 0; + } + fields_fmt('REM','std'); + } elsif (m/^(getpasswd|getshadow|getgroup)\s+(.*\S)$/) { + must_eval("\$ch_$1= \$2; 1;"); + } elsif (m/^(local|remote)format\s+(\w+)$/) { + fields_fmt($1 eq 'local' ? 'PW' : + $1 eq 'remote' ? 'REM' : die, + $2); + } elsif (m/^lock(passwd|group)\s+(runvia|link)\s+(\S+)$/) { + must_eval("\$ch_lock_$1= \$3; \$ch_lockstyle_$1= \$2; 1;"); + } elsif (m/^lock(passwd|group)\s+(none)$/) { + must_eval("\$ch_lockstyle_$1= \$2; 1;"); + } elsif (m,^(homebase|defaultshell)\s+(/\S+)$,) { + must_eval("\$ch_$1= \$2; 1;"); + } elsif (m/^(uidmin|uidmax)\s+(\d+)$/ && $2>0) { + must_eval("\$ch_$1= \$2; 1;"); + } elsif (m/^createuser$/) { + $opt_createuser= $def_createuser; + } elsif (m/^nocreateuser$/) { + $opt_createuser= ''; + } elsif (m/^createuser\s+(\S+)$/) { + $opt_createuser= $1; + } elsif (m/^logfile\s+(.*\S)$/) { + if (!$no_act) { + open STDOUT,">> $1" or die "$1: $!"; $|=1; + $!=0; system 'date'; $? and die "date: $! $?\n"; + } elsif (!$display) { + print "would log to $1\n" or die $!; + } + } elsif (m/^(no|)(sameuid)$/) { + must_eval("\$opt_$2= ".($1 eq 'no' ? 0 : 1)."; 1;"); + } elsif (m/^usergroups$/) { + $defaultgid= -1; + } elsif (m/^nousergroups$/) { + $defaultgid= -2; + } elsif (m/^defaultgid\s+(\d+)$/) { + $defaultgid= $1; + } elsif (m/^(no|)group\s+([-+.0-9a-zA-Z*?]+)$/) { + $yes= $1 eq 'no' ? 0 : 1; + $_= $2; + @groupglobs=() if $_ eq '*'; + s/[-+._]/\\$&/g; + s/\*/\.\*/g; + s/\?/\./g; + unshift @groupglobs, [ $_, $yes ]; + regroupglobs(); + } elsif (m/^user\s+(\S+)$/) { + syncuser($1,$1); + } elsif (m/^user\s+(\S+)\s+remote\=(\S+)$/) { + syncuser($1,$2); + } elsif (m/^nouser\s+(\S+)$/) { + syncuser($1,''); + } elsif (m/^users\s+(\d+)\-(\d+)$/) { + $tmin= $1; $tmax= $2; $except= $3; + fetchpasswd(); + for $k (keys %rempasswd) { + $tuid= $rempasswd{$k}->[2]; + next if $tuid<$1 or $tuid>$2; + syncuser($k,$k); + } + } elsif (m/^addhere$/) { + } else { + die "$configfile:$.: unknown directive\n"; + } +} + +die "$configfile:$.: missing \`end', or read error\n"; diff --git a/sync-accounts/sync-accounts-createuser b/sync-accounts/sync-accounts-createuser new file mode 100755 index 0000000..a8f7904 --- /dev/null +++ b/sync-accounts/sync-accounts-createuser @@ -0,0 +1,28 @@ +#!/bin/sh +# $Id: sync-accounts-createuser,v 1.6 2007-09-21 21:21:15 ianmdlvl Exp $ +# +# Copyright 1999-2002 Ian Jackson +# +# This is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# This is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should already have a copy of the GNU General Public License. +# If not, consult the Free Software Foundation's website at +# www.fsf.org, or the GNU Project website at www.gnu.org. + +set -e +un=$SYNCUSER_CREATE_USER +ui=$SYNCUSER_CREATE_UID +gi=$SYNCUSER_CREATE_GID +ho=$SYNCUSER_CREATE_HOME +test -d $ho || mkdir $ho +chgrp $gi $ho +chown $ui $ho +chmod 2755 $ho diff --git a/sync-accounts/sync-accounts-createuser.8 b/sync-accounts/sync-accounts-createuser.8 new file mode 100644 index 0000000..ffd897d --- /dev/null +++ b/sync-accounts/sync-accounts-createuser.8 @@ -0,0 +1,101 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.TH SYNC\-ACCOUNTS\-CREATEUSER 8 "14th July 2002" "Greenend" "chiark utilities" +.SH NAME +sync\-accounts\-createuser \- helper/hook program for sync\-accounts +.SH SYNOPSIS +.BI SYNCUSER_CREATE_ var = "value\fP... \fI" sync\-accounts\-createuser +.SH DESCRIPTION +.B sync-accounts-createuser +is invoked by +.B sync-accounts +when sync-accounts is creating a local account. + +It must perform all of the tasks involved with local account creation +except for the actual changes to the password, shadow and group +databases. + +At the very minimum, it must create the new account's home directory +(with appropriate permissions). The supplied sync-accounts-createuser +script does exactly that. + +It may also suggest to sync-accounts modifications to the new +account's passwd entry. +.SH INVOCATION +When sync-accounts-createuser is invoked, the passwd and group entries +will not yet have been set up, so it may not rely on them. +sync-accounts-createuser will not be supplied with any arguments. +However, the following environment variables will be set, giving +details about the account to be created: +.br +.B " " SYNCUSER_CREATE_USER +.br +.B " " SYNCUSER_CREATE_UID +.br +.B " " SYNCUSER_CREATE_GID +.br +.B " " SYNCUSER_CREATE_COMMENT +.br +.B " " SYNCUSER_CREATE_HOME +.br +.B " " SYNCUSER_CREATE_SHELL +.SH RESULTS +sync-accounts-createuser should usually produce no output. + +It can inhibit the creation of the user by outputting a single line +not containing a colon; in this case, a diagnostic message will be +written to sync-accounts's logfile, and the user will be skipped. + +Alternatively, it may write out an alternative password file entry, in +which case sync-accounts will use the supplied data for the local +passwd file instead of that from the remote host. +The line should be in +Sys-V passwd file format (regardless of +.B localformat +or +.B remoteformat +settings). The username field should be taken from +.BR SYNCUSER_CREATE_USER , +and the password field should be +.BR x . +.SH EXIT STATUS +.TP +.B 0 +All went well, or we wrote a line without a colon to say +that the account should not be created. +.TP +any other +There were serious problems and sync-accounts should bomb out +immediately. +.SH FILES +None. +.SH ENVIRONMENT +See above. +.SH BUGS +The supplied sync-accounts-createuser does not check that it +was not supplied with any arguments; nor does it check that the +.B SYNCUSER_CREATE_* +variables are set, or have sensible values. +.SH AUTHOR +.B sync-accounts-createuser +and this manpage were written by Ian Jackson +. They are Copyright 1999-2002 Ian +Jackson . This manpage forms part of +the sync-accounts package. + +sync-accounts-createuser and the sync-accounts package are free +software; you can redistribute it and/or modify it under the terms of +the GNU General Public License as published by the Free Software +Foundation; either version 3, or (at your option) any later version. + +This is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with this program; if not, consult the Free Software Foundation's +website at www.fsf.org, or the GNU Project website at www.gnu.org. +.SH SEE ALSO +.BR sync-accounts "(8), " +.BR sync-accounts "(5), " +.BR passwd "(5)" diff --git a/sync-accounts/sync-accounts.5 b/sync-accounts/sync-accounts.5 new file mode 100644 index 0000000..c5d198b --- /dev/null +++ b/sync-accounts/sync-accounts.5 @@ -0,0 +1,396 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.TH SYNC\-ACCOUNTS 5 "15th July 2002" "Greenend" "chiark utilities" +.SH NAME +/etc/sync\-accounts \- configuration file for sync\-accounts +.SH DESCRIPTION +.B /etc/sync\-accounts +contains the default configuration of the +.BR sync-accounts (8) +account synchronisation tool. + +The configuration file specifies how to access and update the local +password and group databases, where sync-accounts should log. + +It also specifies the list of (remote) sources for account +information, and which accounts and details should be copied from each +source to the local system. +.SH OVERALL SYNTAX AND SEMANTICS +The configuration file is parsed as a series of lines. First, leading +and trailing whitespace on each line is removed, and then empty lines, +or lines starting with +.BR # , +are removed. + +Each line is parsed as a directive. The order of directives is +significant; some directives set up information which later +directives rely on. + +The configuration file must contain an +.B end +directive; anything after that point is ignored. +.SH GLOBAL DIRECTIVES +These directives may appear only at the start of the file (before any +other directives), and each directive must appear only once; +otherwise, sync-accounts my behave oddly. +.TP +.BR lockpasswd | lockgroup " \fImethod\fP [\fIdetails \fP...]" +Specifies how the passwd and group files should be read and/or locked. +See +.B LOCKING METHOD DIRECTIVES +below. +.TP +.BI "logfile " filename +Append log messages to +.I filename +instead of stdout. +Errors still go to stderr. +.TP +.BR localformat " " bsd | std +Specifies the local password file is in the relevant format: +.B std +is the standard V7 password file (with a SysV-style +/etc/shadow if /etc/shadow exists). +.B bsd +is the BSD4.4 master.passwd format, and should be used only with +.BR "lockpasswd runvia vipw" . +The default is +.BR std . +.SH LOCKING METHOD DIRECTIVES +One +.B lockgroup +and one +.B lockpasswd +directive must be present, in the global directives at the start of +the config file. + +The choice of the appropriate directives can be difficult without +special knowledge of the local system. In general, it is best to use +.B lockpasswd runvia vipw +where this is available, as if this works avoids having to know the +names of the lockfiles. + +GNU systems (including GNU/Linux and Debian GNU/BSD) typically lock +the group file separately and supply +.BR vigr , +in which case you should use +.BR "lockgroup vigr" . + +Most systems other than GNU do not lock the group file at all (or +assume that all programs which modify the group file will lock the +passwd file), in which case +.B lockgroup none +is appropriate. + +If vigr or vipw is not available or is known to be broken (eg, because +it does not lock properly), then use +.BR link . +.TP +.BR lockpasswd | lockgroup " " runvia " \fIprogram\fP +sync-accounts will reinvoke itself using +.IR program , +which must behave like +.B vipw +or +.BR vigr . +sync-accounts will set the +.B EDITOR +environment variable to the path it was invoked with (Perl's +.BR $0 ) +and put some information for its own use into +.B SYNC_ACCOUNTS_* +environment variables (which will also allow sync-accounts to tell +that it has already been reinvoked via +.I program +and should not do so again). + +If both +.BI "lockpasswd runvia " vipw +and +.BI "lockgroup runvia " vigr +are specified, then it must be possible and safe for the EDITOR +run by +.I vipw +to invoke +.IR vigr , +as this is what sync-accounts will do. +.TP +.BR lockpasswd | lockgroup " " link " \fIsuffix\fP|\fIfilename\fP +sync-accounts will attempt to lock the passwd or group file by making +a hardlink from the real file to the specified filename. +If +.IR suffix | filename +starts with a +.B / +it is interpreted as a filename; otherwise it is interpreted as +a suffix, to be appended to the real database filename. +.TP +.BR lockpasswd | lockgroup " " none +sync-accounts will not attempt to lock the passwd or group files at +all. + +.B lockgroup none +is appropriate on systems where there is no separate locking for the +group file (either because there is no proper support for automatic +editing of the group file, or because you're expected to lock the +password file), although in the absence of +.B vigr +it's inevitable that simultaneous changes to the group file made by +both the human sysadmin and by sync-accounts will cause problems. + +.B lockpasswd none +is very dangerous and should not normally be used. It will cause data +loss if any other tool for changing password data is used - eg, +.BR passwd (1). +.SH PER-SOURCE DIRECTIVES +Within each source's section, all of the per-source directives must +appear before any account-selection directives; otherwise +sync-accounts may behave oddly. If a per-source directive is +repeated, the last setting takes effect. +.TP +.BI "host " source +Starts a source's section. Usually each source will correspond +exactly to one host which is acting as a source of account data. +The +.B host +directive resets the per-source parameters to the defaults. +.I source +need not be the source host's official name in any sense and is used +only for identification. Any +.I source +must be named in only one +.B host +directive, or sync-accounts may behave oddly. +.TP +.BR getpasswd | getgroup | getshadow " \fIcommand\fP..." +sync-accounts always fetches account data from sources by running specified +commands on the local host; it does not contain any network protocols, +itself. + +.I command +is fed to +.BR "sh -c" +and might typically contain something like +.br +.B " ssh syncacct@remote.host cat /etc/passwd" +.br +where the user syncacct on remote.host is in group shadow, or +.br +.B " cat /var/local/sync-accounts/remote.host/passwd" +where the file named is copied across using cron. + +.B getpasswd +must be specified if user data is to be transferred; +.B getgroup +must be specified if group data is to be transferred. + +.B getshadow +should be specified iff getpasswd is specified but the data from +getpasswd does not contain actual password information, and should +emit data in Sys-V shadow password format. +.TP +.BR remoteformat " " std | bsd +Specifies the format of the output of getpasswd. +.B std +is standard V7 passwd file format (optionally augmented by the use of +a shadow file fetched with getshadow). +.B bsd +is the BSD4.4 master.passwd format (and getshadow should not normally +be used with +.BR "remoteformat bsd" ). +The default is +.BR std . +.SH SYNCHRONISATION SETTINGS +The following directives affect the way that account data is copied. +They may be freely mixed with other directives, and repeated. The +setting in effect is the one set by the last relevant settings +directive before any particular account-selection directive. +.TP +.BR uidmin | uidmax " \fivalue\fP" +When an account is to be created locally, a uid/gid will be chosen +which is one higher than the highest currently in use, except that ids +below uidmin or above uidmax are ignored and will never be used. +There is no default. +.TP +.BI "homebase " homebase +When an account is to be created locally, its home directory will be +.IB homebase / username +where +.I username +is the name of the account. The default is +.BR /home . +.TP +.RB [ no ] sameuid +Specifies whether uids are supposed to match. With +.BR sameuid , +it is an error for the uid or gid of a synchronised local account not +to match the corresponding remote account, and new local accounts will +get the remote accounts' ids. +The default is +.BR nosameuid . +.TP +.BR usergroups " | " nousergroups " | " defaultgid " \fIgid\fP" +Specifies whether local accounts are supposed to have +corresponding groups, or all be part of a particular group. The +default is +.BR usergroups . + +With +.BR usergroups , +when a new account is created, the +corresponding per-user group will be created as well, and +per-user groups are created for existing accounts if necessary +(if account creation is enabled). If the gid or group name for +a per-user group is already taken for a different group name or +gid this will be logged, and processing of that account will be +inhibited, but it is not a fatal error. + +With +.BR defaultgid , +newly-created accounts will be made a part of that group, +and the groups of existing accounts will be left alone. + +With +.BR nousergroups , +no new accounts can be created, and existing accounts' groups will be +left alone. +.TP +.BR createuser " [\fIcommand\fP] | " nocreateuser +Specifies whether accounts found on the remote host should be created +if necessary, and what command to run to do the the rest of the +account setup (eg, creation of home directory, etc.). The default is +.BR nocreateuser . + +If +.B createuser +is specified without a command then +.B sync-accounts-createuser +is used; the supplied sync-accounts-createuser program is a reasonable +minimal implementation. + +With +.BR createuser , +either sameuid, or both uidmin and uidmax, must be specified, if +accounts are actually to be created. + +The command is passed to +.BR "sh -c" . +See +.BR sync-accounts-createuser (8) +for details of +.IR command 's +environment and functionality. +.TP +.BR group | nogroup " \fIglob-pattern\fP" +.B group +specifies that the membership of the local groups specified should be +adjusted adjusted whenever account data for any user is copied, so +that the account will be a member of the affected group locally iff +the source account it is a member of the same group on the source +host. + +The most recently-encountered glob-pattern for a particular group +takes effect. The default is +.BR "nogroups *" . + +The glob patterns may contain only alphanumerics, the two glob +metacharacters +.BR "* ?" +and four punctuation characters +.BR "- + . _" ; +\fB\\\fP-quoting and character sets and ranges are not supported. +.TP +.BI "defaultshell " pathname +Local accounts' shells will, when an account is synchronised, be set +to the remote account's shell if the same file exists locally and is +executable. Otherwise, this value will be used. The +default is +.BR /bin/sh . +.SH ACCOUNT SELECTION +These directives specify that the selected accounts are to be +synchronised: that is, the local account data will be unconditionally +overwritten (according to the synchronisation settings) with data from +the current source (according to the most recent +.B host +directive). + +Any particular local username will only be synchronised once; the +source and settings for first account selection directive which +selects that local username will be used. + +When an account is synchronised, the account password, comment field, +and shell will be copied unconditionally. If +.B sameuid +is in effect specified the uid will be checked (or copied, for new +accounts). +.TP +.BR user " \fIusername\fP [" remote "=\fIremoteusername\fP]" +Specifies that account data should be copied for local user +.I username +from the remote account +.I remoteusername +(or +.I username +if +.I remoteusername +is not specified). +.TP +.RI "\fBusers\fP " ruidmin - ruidmax +Specifies that all remote users whose remote uid is in the given range +are to be synchronised to corresponding user accounts. (Note that the +remote uid will only be copied if +.B sameuid +is in effect.) +.TP +.BI "nouser " username +Specifies that data for +.I username is not to be copied, even +if subsequent user or users directives suggest that it should be. +.TP +.B addhere +This directive has no effect on sync-accounts. However, it is used as +a placeholder by grab-account: new accounts for creation are inserted +just before addhere. See +.BR grab-account (8). +.SH FINAL DIRECTIVE +.TP +.B end +must appear in the configuration file, usually at the end of the file. +Nothing after it will be read. +.SH BUGS +The advice about the correct +.B lockpasswd +and +.B lockgroup +directives is probably out of date or flatly wrong. +.SH AUTHOR +.B sync-accounts +and this manpage are part of the +.B sync-accounts +package which was written by Ian Jackson . +They are Copyright 1999-2000,2002 Ian Jackson +, and Copyright 2000-2001 nCipher +Corporation Ltd. + +The sync-accounts package is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 3, or (at +your option) any later version. + +This is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with this program; if not, consult the Free Software Foundation's +website at www.fsf.org, or the GNU Project website at www.gnu.org. +.SH SEE ALSO +.BR sync-accounts "(8), " +.BR grab-account "(8), " +.BR sync-accounts-createuser "(8), " +.BR passwd "(5), " +.BR group "(5), " +.BR shadow "(5), " +.BR master.passwd "(5), " +.BR vipw "(8), " +.BR vigr "(8)" diff --git a/sync-accounts/sync-accounts.8 b/sync-accounts/sync-accounts.8 new file mode 100644 index 0000000..5e2ca29 --- /dev/null +++ b/sync-accounts/sync-accounts.8 @@ -0,0 +1,155 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.TH SYNC\-ACCOUNTS 8 "14th July 2002" "Greenend" "chiark utilities" +.SH NAME +sync\-accounts \- synchronise accounts and passwords +.SH SYNOPSIS +.BR sync\-accounts " [\fIoptions\fP] [\fIsource\fP ...]" +.SH DESCRIPTION +.B sync-accounts +is a tool for copying account information into the local system's +password and group databases, or equivalent, from other systems. It +can be used to slave individual accounts, whole systems, or various +partial combinations. + +By default, when invoked, sync-accounts reads is configuration file +and updates all of the local details it is configured to synchronise, +from all relevant sources. + +If one or more \fIsource\fPs are named as command-line arguments, only +information from those sources is installed locally. + +See +.BR sync-accounts(5) +for detailed information about sync-accounts's behaviour and +configuration. +.SH OPTIONS +.TP +.BI \-C config\-file +Reads +.I config-file +instead of +.BR /etc/sync-accounts . +.TP +.BR \-q +Instead of updating local information, sync-accounts displays a +summary of which accounts are synchronised or not, and from where. +.TP +.BR \-n +Causes sync-accounts not to actually install the new information in +the local password and group databases. Instead, updated versions are +written to the files +.B passwd +and +.B group +in the current directory. With +.B \-n +new accounts are not created at all. The system databases are not +locked. +.SH SECURITY +sync-accounts is not resistant to malicious data in the local +password and group databases, or its configuration file or command +line arguments. + +Malicious data in source information will not be able to take control +of sync-accounts, but will be copied to the local databases if +sync-accounts is configured to do so. + +To update the local databases, sync-accounts must be run as root. +For \-q and \-n sync-accounts still needs to be able to successfuly +invoke the commands specified in the configuration for getpasswd and +getgroup. +.SH EXIT STATUS +.TP +.B 0 +All went well and there were no warnings. +.TP +any other +There were problems. The local databases may or may not have been +updated. +.SH FILES +.TP +.B /etc/sync-accounts +Default configuration file. (Override with +.BR -C .) +.TP +.B sync-accounts-createuser +Default command invoked by sync-accounts to create local users. +.TP +.B /home +Default location for created users' home directories. +.TP +.B /bin/sh +Default shell for created users. +.TP +.BR /etc/passwd ", " /etc/group ", " /etc/shadow ", " /etc/master.passwd +Local account databases, depending on configuration. +.TP +.BR /etc/shadow-non-existent +Must not exist. +.SH ENVIRONMENT +.TP +.BR EDITOR ", " VISUAL +Manipulated by sync-\accounts when it is reinvoking itself via vipw or +vigr, according to +.B lockpasswd runvia +or +.BR "lockgroup runvia" . +.TP +.BR SYNC_ACCOUNTS_* +Used by sync-accounts for its own purposes. Do not set these +variables. +.LP +Setting variables used by +.BR vipw (8) +and +.BR vigr (8), +apart from +.BR EDITOR " and/or" VISUAL +will affect the operation of sync-accounts. +Avoid messing with these if possible. +.LP +.B PATH +is used to find subprograms such as +.BR sync-accounts-createuser " and " vipw / vigr . +.SH BUGS +Using sync-accounts does not give particularly prompt propagation of +changed account information. + +There is no simple mechanism for automatically getting the right +configuration details for accessing the local system's password and +group databases. + +All the systems sharing account information using sync-accounts need +to be using compatible encrypted-password schemes. +.SH AUTHOR +.B sync-accounts +and this manpage are part of the +.B sync-accounts +package which was written by Ian Jackson . +They are Copyright 1999-2000,2002 Ian Jackson +, and Copyright 2000-2001 nCipher +Corporation Ltd. + +The sync-accounts package is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 3, or (at +your option) any later version. + +This is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with this program; if not, consult the Free Software Foundation's +website at www.fsf.org, or the GNU Project website at www.gnu.org. +.SH SEE ALSO +.BR sync-accounts "(5), " +.BR grab-account "(8), " +.BR sync-accounts-createuser "(8), " +.BR passwd "(5), " +.BR group "(5), " +.BR shadow "(5), " +.BR master.passwd "(5), " +.BR vipw "(8), " +.BR vigr "(8)" diff --git a/sync-accounts/sync-accounts.example-bsd b/sync-accounts/sync-accounts.example-bsd new file mode 100644 index 0000000..e943183 --- /dev/null +++ b/sync-accounts/sync-accounts.example-bsd @@ -0,0 +1,33 @@ +# This is an example /etc/sync-accounts for BSD. + +lockpasswd runvia vipw +lockgroup none +localformat bsd +logfile /var/log/sync-accounts + +uidmin 500 +uidmax 29999 +homebase /home +defaultshell /usr/local/bin/bash + +host fowey +getpasswd rsh fowey -l syncacct cat /etc/passwd +getshadow rsh fowey -l syncacct cat /etc/shadow +getgroup rsh fowey -l syncacct cat /etc/group +createuser sync-accounts-createuser +sameuid +group devel + +nousergroups +user devel +usergroups + +users 500-29999 + +nogroup * +nocreateuser +nousergroups +nosameuid +#user root + +end diff --git a/sync-accounts/sync-accounts.example-linux b/sync-accounts/sync-accounts.example-linux new file mode 100644 index 0000000..73d015f --- /dev/null +++ b/sync-accounts/sync-accounts.example-linux @@ -0,0 +1,76 @@ +# This is an example /etc/sync-accounts for Linux. + +lockpasswd runvia vipw +lockgroup runvia vigr +logfile /var/log/sync-accounts + +uidmin 1000 +uidmax 29999 +homebase /u2 +defaultshell /bin/bash + +host sfere +getpasswd ssh sfere -l ian cat /etc/passwd +createuser +user richardk remote=richard +addhere + +#host snoopy +#getpasswd ssh snoopy -l ian cat /etc/passwd +#getshadow ssh snoopy -l ian really cat /etc/shadow +#createuser +#user christi +#addhere + +host khem +getpasswd rsh khem -l ian cat /etc/passwd +createuser +user andrewm +addhere + +host chiark +getpasswd cat /var/lib/sync-accounts/chiark/passwd +getshadow cat /var/lib/sync-accounts/chiark/shadow +getgroup cat /var/lib/sync-accounts/chiark/group +createuser sync-accounts-createuser-chiark2davenant +sameuid +user richard +user ian + +user ijackson +user owend +user damerell +user jonr +user duncanm +user siona +user dans +user marisal +user vivienh +user davidr +user fanf +user wednsday +user emmaburt +user eleanorb +user stevee +user diziet +user matthewv +user mattheww +user sgtatham +user jdamery +user twomack +user aldabra +user janetmck +user johns +user andreww +user sbleas +user clareb +user chrisj +user beckyc +addhere + +nogroup * +nocreateuser +nousergroups +user root + +end